| /* @file |
| * @brief Bluetooth PACS |
| */ |
| |
| /* |
| * Copyright (c) 2020 Intel Corporation |
| * Copyright (c) 2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/zephyr.h> |
| #include <zephyr/sys/byteorder.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/capabilities.h> |
| #include "../host/conn_internal.h" |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_PACS) |
| #define LOG_MODULE_NAME bt_pacs |
| #include "common/log.h" |
| |
| #include "pacs_internal.h" |
| #include "unicast_server.h" |
| |
| #define PAC_NOTIFY_TIMEOUT K_MSEC(10) |
| |
| NET_BUF_SIMPLE_DEFINE_STATIC(read_buf, CONFIG_BT_L2CAP_TX_MTU); |
| |
| static void pac_data_add(struct net_buf_simple *buf, uint8_t num, |
| struct bt_codec_data *data) |
| { |
| struct bt_pac_codec_capability *cc; |
| int i; |
| |
| for (i = 0; i < num; i++) { |
| struct bt_data *d = &data[i].data; |
| |
| cc = net_buf_simple_add(buf, sizeof(*cc)); |
| cc->len = d->data_len + sizeof(cc->type); |
| cc->type = d->type; |
| net_buf_simple_add_mem(buf, d->data, d->data_len); |
| } |
| } |
| |
| static void get_pac_records(struct bt_conn *conn, enum bt_audio_dir dir, |
| struct net_buf_simple *buf) |
| { |
| struct bt_pacs_read_rsp *rsp; |
| |
| /* Reset if buffer before using */ |
| net_buf_simple_reset(buf); |
| |
| rsp = net_buf_simple_add(&read_buf, sizeof(*rsp)); |
| rsp->num_pac = 0; |
| |
| if (unicast_server_cb == NULL || |
| unicast_server_cb->publish_capability == NULL) { |
| return; |
| } |
| |
| while (true) { |
| struct bt_pac_meta *meta; |
| struct bt_codec codec; |
| struct bt_pac *pac; |
| int err; |
| |
| err = unicast_server_cb->publish_capability(conn, dir, |
| rsp->num_pac, |
| &codec); |
| if (err != 0) { |
| break; |
| } |
| |
| pac = net_buf_simple_add(&read_buf, sizeof(*pac)); |
| |
| pac->codec.id = codec.id; |
| pac->codec.cid = sys_cpu_to_le16(codec.cid); |
| pac->codec.vid = sys_cpu_to_le16(codec.vid); |
| pac->cc_len = read_buf.len; |
| |
| pac_data_add(&read_buf, codec.data_count, codec.data); |
| |
| /* Buffer size shall never be below PAC len since we are just |
| * append data. |
| */ |
| __ASSERT_NO_MSG(read_buf.len >= pac->cc_len); |
| |
| pac->cc_len = read_buf.len - pac->cc_len; |
| |
| meta = net_buf_simple_add(&read_buf, sizeof(*meta)); |
| meta->len = read_buf.len; |
| pac_data_add(&read_buf, codec.meta_count, codec.meta); |
| meta->len = read_buf.len - meta->len; |
| |
| BT_DBG("pac #%u: codec capability len %u metadata len %u", |
| rsp->num_pac, pac->cc_len, meta->len); |
| |
| rsp->num_pac++; |
| } |
| } |
| |
| static ssize_t pac_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| void *buf, uint16_t len, uint16_t offset) |
| { |
| enum bt_audio_dir dir; |
| |
| if (!bt_uuid_cmp(attr->uuid, BT_UUID_PACS_SNK)) { |
| dir = BT_AUDIO_DIR_SINK; |
| } else { |
| dir = BT_AUDIO_DIR_SOURCE; |
| } |
| |
| get_pac_records(conn, dir, &read_buf); |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, |
| read_buf.len); |
| } |
| |
| static void available_context_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) |
| { |
| BT_DBG("attr %p value 0x%04x", attr, value); |
| } |
| |
| static int available_contexts_get(struct bt_conn *conn, struct bt_pacs_context *context) |
| { |
| enum bt_audio_context context_snk, context_src; |
| int err; |
| |
| if (unicast_server_cb == NULL || |
| unicast_server_cb->get_available_contexts == NULL) { |
| BT_WARN("No callback for get_available_contexts"); |
| return -ENODATA; |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_PAC_SNK)) { |
| err = unicast_server_cb->get_available_contexts(conn, |
| BT_AUDIO_DIR_SINK, |
| &context_snk); |
| if (err) { |
| return err; |
| } |
| } else { |
| /* Server is not available to receive audio for any Context */ |
| context_snk = BT_AUDIO_CONTEXT_TYPE_PROHIBITED; |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_PAC_SRC)) { |
| err = unicast_server_cb->get_available_contexts(conn, |
| BT_AUDIO_DIR_SOURCE, |
| &context_src); |
| if (err) { |
| return err; |
| } |
| } else { |
| /* Server is not available to send audio for any Context */ |
| context_src = BT_AUDIO_CONTEXT_TYPE_PROHIBITED; |
| } |
| |
| context->snk = sys_cpu_to_le16((uint16_t)context_snk); |
| context->src = sys_cpu_to_le16((uint16_t)context_src); |
| |
| return 0; |
| } |
| |
| 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; |
| int err; |
| |
| BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len, |
| offset); |
| |
| err = available_contexts_get(NULL, &context); |
| if (err) { |
| BT_DBG("get_available_contexts returned %d", err); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, &context, |
| sizeof(context)); |
| } |
| |
| static void supported_context_cfg_changed(const struct bt_gatt_attr *attr, |
| uint16_t value) |
| { |
| BT_DBG("attr %p value 0x%04x", attr, value); |
| } |
| |
| 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; |
| |
| if (IS_ENABLED(CONFIG_BT_PAC_SNK)) { |
| context.snk = sys_cpu_to_le16(CONFIG_BT_PACS_SNK_CONTEXT); |
| } else { |
| context.snk = BT_AUDIO_CONTEXT_TYPE_PROHIBITED; |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_PAC_SRC)) { |
| context.src = sys_cpu_to_le16(CONFIG_BT_PACS_SRC_CONTEXT); |
| } else { |
| context.src = BT_AUDIO_CONTEXT_TYPE_PROHIBITED; |
| } |
| |
| BT_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_PAC_SNK_LOC) || defined(CONFIG_BT_PAC_SRC_LOC) |
| static int get_pac_loc(struct bt_conn *conn, enum bt_audio_dir dir, |
| enum bt_audio_location *location) |
| { |
| int err; |
| |
| if (unicast_server_cb == NULL || |
| unicast_server_cb->publish_location == NULL) { |
| BT_WARN("No callback for publish_location"); |
| return -ENODATA; |
| } |
| |
| err = unicast_server_cb->publish_location(conn, dir, location); |
| if (err != 0 || *location == 0) { |
| BT_DBG("err (%d) or invalid location value (%u)", |
| err, *location); |
| return -ENODATA; |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_BT_PAC_SNK_LOC || CONFIG_BT_PAC_SRC_LOC */ |
| |
| #if defined(CONFIG_BT_PAC_SNK) |
| static struct k_work_delayable snks_work; |
| |
| static ssize_t snk_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| void *buf, uint16_t len, uint16_t offset) |
| { |
| BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len, |
| offset); |
| |
| return pac_read(conn, attr, buf, len, offset); |
| } |
| |
| static void snk_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) |
| { |
| BT_DBG("attr %p value 0x%04x", attr, value); |
| } |
| |
| #if defined(CONFIG_BT_PAC_SNK_LOC) |
| static struct k_work_delayable snks_loc_work; |
| |
| static ssize_t snk_loc_read(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, void *buf, |
| uint16_t len, uint16_t offset) |
| { |
| int err; |
| enum bt_audio_location location; |
| uint32_t location_32; |
| uint32_t location_32_le; |
| |
| BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len, |
| offset); |
| |
| err = get_pac_loc(NULL, BT_AUDIO_DIR_SINK, &location); |
| if (err != 0) { |
| BT_DBG("get_pac_loc returned %d", err); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| location_32 = (uint32_t)location; |
| if (location_32 > BT_AUDIO_LOCATION_MASK || location_32 == 0) { |
| BT_ERR("Invalid location value: 0x%08X", location_32); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| location_32_le = sys_cpu_to_le32(location_32); |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, |
| &location_32_le, sizeof(location_32_le)); |
| } |
| |
| #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) |
| { |
| int err; |
| 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_INVALID_ATTRIBUTE_LEN); |
| } |
| |
| if (unicast_server_cb == NULL || |
| unicast_server_cb->write_location == NULL) { |
| BT_WARN("No callback for write_location"); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| location = (enum bt_audio_location)sys_get_le32(data); |
| if (location > BT_AUDIO_LOCATION_MASK || location == 0) { |
| BT_DBG("Invalid location value: 0x%08X", location); |
| return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); |
| } |
| |
| err = unicast_server_cb->write_location(conn, BT_AUDIO_DIR_SINK, |
| location); |
| if (err != 0) { |
| BT_DBG("write_location returned %d", err); |
| return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION); |
| } |
| |
| return len; |
| } |
| #endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE */ |
| |
| static void snk_loc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) |
| { |
| BT_DBG("attr %p value 0x%04x", attr, value); |
| } |
| |
| #endif /* CONFIG_BT_PAC_SNK_LOC */ |
| #endif /* CONFIG_BT_PAC_SNK */ |
| |
| #if defined(CONFIG_BT_PAC_SRC) |
| static struct k_work_delayable srcs_work; |
| |
| static ssize_t src_read(struct bt_conn *conn, const struct bt_gatt_attr *attr, |
| void *buf, uint16_t len, uint16_t offset) |
| { |
| BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len, |
| offset); |
| |
| return pac_read(conn, attr, buf, len, offset); |
| } |
| |
| static void src_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) |
| { |
| BT_DBG("attr %p value 0x%04x", attr, value); |
| } |
| |
| #if defined(CONFIG_BT_PAC_SRC_LOC) |
| static struct k_work_delayable srcs_loc_work; |
| |
| static ssize_t src_loc_read(struct bt_conn *conn, |
| const struct bt_gatt_attr *attr, void *buf, |
| uint16_t len, uint16_t offset) |
| { |
| int err; |
| enum bt_audio_location location; |
| uint32_t location_32; |
| uint32_t location_32_le; |
| |
| BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len, |
| offset); |
| |
| err = get_pac_loc(NULL, BT_AUDIO_DIR_SOURCE, &location); |
| if (err != 0) { |
| BT_DBG("get_pac_loc returned %d", err); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| location_32 = (uint32_t)location; |
| if (location_32 > BT_AUDIO_LOCATION_MASK || location_32 == 0) { |
| BT_ERR("Invalid location value: 0x%08X", location_32); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| location_32_le = sys_cpu_to_le32(location_32); |
| |
| return bt_gatt_attr_read(conn, attr, buf, len, offset, |
| &location_32_le, sizeof(location_32_le)); |
| } |
| |
| #if defined(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) |
| { |
| int err; |
| 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_INVALID_ATTRIBUTE_LEN); |
| } |
| |
| if (unicast_server_cb == NULL || |
| unicast_server_cb->write_location == NULL) { |
| BT_WARN("No callback for write_location"); |
| return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); |
| } |
| |
| location = (enum bt_audio_location)sys_get_le32(data); |
| if (location > BT_AUDIO_LOCATION_MASK || location == 0) { |
| BT_DBG("Invalid location value: 0x%08X", location); |
| return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); |
| } |
| |
| err = unicast_server_cb->write_location(conn, BT_AUDIO_DIR_SOURCE, |
| location); |
| if (err != 0) { |
| BT_DBG("write_location returned %d", err); |
| return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION); |
| } |
| |
| return len; |
| } |
| #endif /* BT_PAC_SRC_LOC_WRITEABLE */ |
| |
| static void src_loc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) |
| { |
| BT_DBG("attr %p value 0x%04x", attr, value); |
| } |
| #endif /* CONFIG_BT_PAC_SRC_LOC */ |
| #endif /* CONFIG_BT_PAC_SRC */ |
| |
| BT_GATT_SERVICE_DEFINE(pacs_svc, |
| BT_GATT_PRIMARY_SERVICE(BT_UUID_PACS), |
| #if defined(CONFIG_BT_PAC_SNK) |
| BT_GATT_CHARACTERISTIC(BT_UUID_PACS_SNK, |
| BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, |
| BT_GATT_PERM_READ_ENCRYPT, |
| snk_read, NULL, NULL), |
| BT_GATT_CCC(snk_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), |
| #if defined(CONFIG_BT_PAC_SNK_LOC) |
| BT_GATT_CHARACTERISTIC(BT_UUID_PACS_SNK_LOC, |
| BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | |
| BT_GATT_CHRC_NOTIFY, |
| BT_GATT_PERM_READ_ENCRYPT | |
| BT_GATT_PERM_WRITE_ENCRYPT, |
| snk_loc_read, |
| #if defined(CONFIG_BT_PAC_SNK_LOC_WRITEABLE) |
| snk_loc_write, |
| #else |
| NULL, |
| #endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE */ |
| NULL), |
| BT_GATT_CCC(snk_loc_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), |
| #endif /* CONFIG_BT_PAC_SNK_LOC */ |
| #endif /* CONFIG_BT_PAC_SNK */ |
| #if defined(CONFIG_BT_PAC_SRC) |
| BT_GATT_CHARACTERISTIC(BT_UUID_PACS_SRC, |
| BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, |
| BT_GATT_PERM_READ_ENCRYPT, |
| src_read, NULL, NULL), |
| BT_GATT_CCC(src_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), |
| #if defined(CONFIG_BT_PAC_SRC_LOC) |
| BT_GATT_CHARACTERISTIC(BT_UUID_PACS_SRC_LOC, |
| BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | |
| BT_GATT_CHRC_NOTIFY, |
| BT_GATT_PERM_READ_ENCRYPT | |
| BT_GATT_PERM_WRITE_ENCRYPT, |
| src_loc_read, |
| #if defined(CONFIG_BT_PAC_SRC_LOC_WRITEABLE) |
| src_loc_write, |
| #else |
| NULL, |
| #endif /* CONFIG_BT_PAC_SRC_LOC_WRITEABLE */ |
| NULL), |
| BT_GATT_CCC(src_loc_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), |
| #endif /* CONFIG_BT_PAC_SRC_LOC */ |
| #endif /* CONFIG_BT_PAC_SNK */ |
| BT_GATT_CHARACTERISTIC(BT_UUID_PACS_AVAILABLE_CONTEXT, |
| BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, |
| BT_GATT_PERM_READ_ENCRYPT, |
| available_contexts_read, NULL, NULL), |
| BT_GATT_CCC(available_context_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT), |
| BT_GATT_CHARACTERISTIC(BT_UUID_PACS_SUPPORTED_CONTEXT, |
| BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, |
| BT_GATT_PERM_READ_ENCRYPT, |
| supported_context_read, NULL, NULL), |
| BT_GATT_CCC(supported_context_cfg_changed, |
| BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) |
| ); |
| |
| static struct k_work_delayable *bt_pacs_get_work(enum bt_audio_dir dir) |
| { |
| switch (dir) { |
| #if defined(CONFIG_BT_PAC_SNK) |
| case BT_AUDIO_DIR_SINK: |
| return &snks_work; |
| #endif /* CONFIG_BT_PAC_SNK */ |
| #if defined(CONFIG_BT_PAC_SRC) |
| case BT_AUDIO_DIR_SOURCE: |
| return &srcs_work; |
| #endif /* CONFIG_BT_PAC_SRC */ |
| default: |
| return NULL; |
| } |
| } |
| |
| #if defined(CONFIG_BT_PAC_SNK_LOC) || defined(CONFIG_BT_PAC_SRC_LOC) |
| static struct k_work_delayable *bt_pacs_get_loc_work(enum bt_audio_dir dir) |
| { |
| switch (dir) { |
| #if defined(CONFIG_BT_PAC_SNK) |
| case BT_AUDIO_DIR_SINK: |
| return &snks_loc_work; |
| #endif /* CONFIG_BT_PAC_SNK */ |
| #if defined(CONFIG_BT_PAC_SRC) |
| case BT_AUDIO_DIR_SOURCE: |
| return &srcs_loc_work; |
| #endif /* CONFIG_BT_PAC_SRC */ |
| default: |
| return NULL; |
| } |
| } |
| |
| static void pac_notify_loc(struct k_work *work) |
| { |
| uint32_t location; |
| struct bt_uuid *uuid; |
| enum bt_audio_dir dir; |
| int err; |
| |
| #if defined(CONFIG_BT_PAC_SNK) |
| if (work == &snks_loc_work.work) { |
| dir = BT_AUDIO_DIR_SINK; |
| uuid = BT_UUID_PACS_SNK_LOC; |
| } |
| #endif /* CONFIG_BT_PAC_SNK */ |
| |
| #if defined(CONFIG_BT_PAC_SRC) |
| if (work == &srcs_loc_work.work) { |
| dir = BT_AUDIO_DIR_SOURCE; |
| uuid = BT_UUID_PACS_SRC_LOC; |
| } |
| #endif /* CONFIG_BT_PAC_SRC */ |
| |
| /* TODO: We can skip this if we are not connected to any devices */ |
| err = get_pac_loc(NULL, dir, &location); |
| if (err != 0) { |
| BT_DBG("get_pac_loc returned %d, won't notify", err); |
| return; |
| } |
| |
| err = bt_gatt_notify_uuid(NULL, uuid, pacs_svc.attrs, |
| &sys_cpu_to_le32(location), |
| sizeof(location)); |
| if (err != 0) { |
| BT_WARN("PACS notify failed: %d", err); |
| } |
| } |
| #endif /* CONFIG_BT_PAC_SNK_LOC || CONFIG_BT_PAC_SRC_LOC */ |
| |
| static void pac_notify(struct k_work *work) |
| { |
| struct bt_uuid *uuid; |
| enum bt_audio_dir dir; |
| int err; |
| |
| #if defined(CONFIG_BT_PAC_SNK) |
| if (work == &snks_work.work) { |
| dir = BT_AUDIO_DIR_SINK; |
| uuid = BT_UUID_PACS_SNK; |
| } |
| #endif /* CONFIG_BT_PAC_SNK */ |
| |
| #if defined(CONFIG_BT_PAC_SRC) |
| if (work == &srcs_work.work) { |
| dir = BT_AUDIO_DIR_SOURCE; |
| uuid = BT_UUID_PACS_SRC; |
| } |
| #endif /* CONFIG_BT_PAC_SRC */ |
| |
| /* TODO: We can skip this if we are not connected to any devices */ |
| get_pac_records(NULL, dir, &read_buf); |
| |
| err = bt_gatt_notify_uuid(NULL, uuid, pacs_svc.attrs, read_buf.data, |
| read_buf.len); |
| if (err != 0) { |
| BT_WARN("PACS notify failed: %d", err); |
| } |
| } |
| |
| void bt_pacs_add_capability(enum bt_audio_dir dir) |
| { |
| struct k_work_delayable *work; |
| |
| work = bt_pacs_get_work(dir); |
| if (!work) { |
| return; |
| } |
| |
| /* Initialize handler if it hasn't been initialized */ |
| if (!work->work.handler) { |
| k_work_init_delayable(work, pac_notify); |
| } |
| |
| k_work_reschedule(work, PAC_NOTIFY_TIMEOUT); |
| } |
| |
| void bt_pacs_remove_capability(enum bt_audio_dir dir) |
| { |
| struct k_work_delayable *work; |
| |
| work = bt_pacs_get_work(dir); |
| if (!work) { |
| return; |
| } |
| |
| k_work_reschedule(work, PAC_NOTIFY_TIMEOUT); |
| } |
| |
| #if defined(CONFIG_BT_PAC_SNK_LOC) || defined(CONFIG_BT_PAC_SRC_LOC) |
| int bt_pacs_location_changed(enum bt_audio_dir dir) |
| { |
| struct k_work_delayable *work; |
| |
| work = bt_pacs_get_loc_work(dir); |
| if (!work) { |
| return -EINVAL; |
| } |
| |
| /* Initialize handler if it hasn't been initialized */ |
| if (!work->work.handler) { |
| k_work_init_delayable(work, pac_notify_loc); |
| } |
| |
| k_work_reschedule(work, PAC_NOTIFY_TIMEOUT); |
| |
| return 0; |
| } |
| #endif /* CONFIG_BT_PAC_SNK_LOC || CONFIG_BT_PAC_SRC_LOC */ |
| |
| static void available_contexts_notify(struct k_work *work) |
| { |
| struct bt_pacs_context context; |
| int err; |
| |
| err = available_contexts_get(NULL, &context); |
| if (err) { |
| BT_DBG("get_available_contexts returned %d, won't notify", err); |
| return; |
| } |
| |
| err = bt_gatt_notify_uuid(NULL, BT_UUID_PACS_AVAILABLE_CONTEXT, pacs_svc.attrs, |
| &context, sizeof(context)); |
| if (err) { |
| BT_WARN("Available Audio Contexts notify failed: %d", err); |
| } |
| } |
| |
| static K_WORK_DELAYABLE_DEFINE(available_contexts_work, available_contexts_notify); |
| |
| int bt_pacs_available_contexts_changed(void) |
| { |
| int err; |
| |
| err = k_work_reschedule(&available_contexts_work, PAC_NOTIFY_TIMEOUT); |
| if (err < 0) { |
| return err; |
| } |
| |
| return 0; |
| } |