2022-01-07 17:23:45 +01:00
|
|
|
/* @file
|
|
|
|
* @brief Bluetooth PACS
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2020 Intel Corporation
|
|
|
|
* Copyright (c) 2022 Nordic Semiconductor ASA
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
|
2022-05-06 11:12:04 +02:00
|
|
|
#include <zephyr/zephyr.h>
|
|
|
|
#include <zephyr/sys/byteorder.h>
|
2022-04-12 16:11:18 +02:00
|
|
|
#include <zephyr/sys/check.h>
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-05-06 11:12:04 +02:00
|
|
|
#include <zephyr/device.h>
|
|
|
|
#include <zephyr/init.h>
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-05-06 11:12:04 +02:00
|
|
|
#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>
|
2022-01-07 17:23:45 +01:00
|
|
|
#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"
|
|
|
|
|
2022-01-31 11:28:59 +01:00
|
|
|
#define PAC_NOTIFY_TIMEOUT K_MSEC(10)
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
NET_BUF_SIMPLE_DEFINE_STATIC(read_buf, CONFIG_BT_L2CAP_TX_MTU);
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
static const struct bt_audio_pacs_cb *pacs_cb;
|
|
|
|
|
2022-04-05 11:50:38 +02:00
|
|
|
static struct bt_pacs_context available_context = {
|
|
|
|
/* TODO: This should reflect the ongoing channel contexts */
|
|
|
|
.snk = BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED,
|
|
|
|
.src = BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED,
|
|
|
|
};
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 17:55:51 +02:00
|
|
|
static void get_pac_records(struct bt_conn *conn, enum bt_audio_dir dir,
|
2022-01-31 14:28:59 +01:00
|
|
|
struct net_buf_simple *buf)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
|
|
|
struct bt_pacs_read_rsp *rsp;
|
|
|
|
|
|
|
|
/* Reset if buffer before using */
|
2022-01-31 14:28:59 +01:00
|
|
|
net_buf_simple_reset(buf);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-07-05 15:06:19 +02:00
|
|
|
rsp = net_buf_simple_add(buf, sizeof(*rsp));
|
2022-01-07 17:23:45 +01:00
|
|
|
rsp->num_pac = 0;
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
if (pacs_cb == NULL ||
|
|
|
|
pacs_cb->publish_capability == NULL) {
|
2022-01-31 14:28:59 +01:00
|
|
|
return;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
2022-01-31 14:28:59 +01:00
|
|
|
struct bt_pac_meta *meta;
|
2022-01-07 17:23:45 +01:00
|
|
|
struct bt_codec codec;
|
|
|
|
struct bt_pac *pac;
|
2022-01-31 14:28:59 +01:00
|
|
|
int err;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
err = pacs_cb->publish_capability(conn, dir, rsp->num_pac,
|
|
|
|
&codec);
|
2022-01-07 17:23:45 +01:00
|
|
|
if (err != 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-07-05 15:06:19 +02:00
|
|
|
pac = net_buf_simple_add(buf, sizeof(*pac));
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
pac->codec.id = codec.id;
|
|
|
|
pac->codec.cid = sys_cpu_to_le16(codec.cid);
|
|
|
|
pac->codec.vid = sys_cpu_to_le16(codec.vid);
|
2022-07-05 15:06:19 +02:00
|
|
|
pac->cc_len = buf->len;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-07-05 15:06:19 +02:00
|
|
|
pac_data_add(buf, codec.data_count, codec.data);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
/* Buffer size shall never be below PAC len since we are just
|
|
|
|
* append data.
|
|
|
|
*/
|
2022-07-05 15:06:19 +02:00
|
|
|
__ASSERT_NO_MSG(buf->len >= pac->cc_len);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-07-05 15:06:19 +02:00
|
|
|
pac->cc_len = buf->len - pac->cc_len;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-07-05 15:06:19 +02:00
|
|
|
meta = net_buf_simple_add(buf, sizeof(*meta));
|
|
|
|
meta->len = buf->len;
|
|
|
|
pac_data_add(buf, codec.meta_count, codec.meta);
|
|
|
|
meta->len = buf->len - meta->len;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
BT_DBG("pac #%u: codec capability len %u metadata len %u",
|
|
|
|
rsp->num_pac, pac->cc_len, meta->len);
|
|
|
|
|
|
|
|
rsp->num_pac++;
|
|
|
|
}
|
2022-01-31 14:28:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t pac_read(struct bt_conn *conn, const struct bt_gatt_attr *attr,
|
|
|
|
void *buf, uint16_t len, uint16_t offset)
|
|
|
|
{
|
2022-03-29 17:55:51 +02:00
|
|
|
enum bt_audio_dir dir;
|
2022-01-31 14:28:59 +01:00
|
|
|
|
|
|
|
if (!bt_uuid_cmp(attr->uuid, BT_UUID_PACS_SNK)) {
|
2022-03-29 18:20:10 +02:00
|
|
|
dir = BT_AUDIO_DIR_SINK;
|
2022-01-31 14:28:59 +01:00
|
|
|
} else {
|
2022-03-29 18:20:10 +02:00
|
|
|
dir = BT_AUDIO_DIR_SOURCE;
|
2022-01-31 14:28:59 +01:00
|
|
|
}
|
|
|
|
|
2022-03-29 17:55:51 +02:00
|
|
|
get_pac_records(conn, dir, &read_buf);
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data,
|
|
|
|
read_buf.len);
|
|
|
|
}
|
|
|
|
|
2022-05-19 16:52:44 +02:00
|
|
|
static void available_context_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
|
|
|
BT_DBG("attr %p value 0x%04x", attr, value);
|
|
|
|
}
|
|
|
|
|
2022-05-19 16:52:44 +02:00
|
|
|
static int available_contexts_get(struct bt_conn *conn, struct bt_pacs_context *context)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
2022-05-19 16:52:44 +02:00
|
|
|
enum bt_audio_context context_snk, context_src;
|
|
|
|
int err;
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
if (pacs_cb == NULL ||
|
|
|
|
pacs_cb->get_available_contexts == NULL) {
|
2022-05-19 16:52:44 +02:00
|
|
|
BT_WARN("No callback for get_available_contexts");
|
|
|
|
return -ENODATA;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PAC_SNK)) {
|
2022-04-12 16:11:18 +02:00
|
|
|
err = pacs_cb->get_available_contexts(conn, BT_AUDIO_DIR_SINK,
|
|
|
|
&context_snk);
|
2022-05-19 16:52:44 +02:00
|
|
|
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)) {
|
2022-04-12 16:11:18 +02:00
|
|
|
err = pacs_cb->get_available_contexts(conn, BT_AUDIO_DIR_SOURCE,
|
|
|
|
&context_src);
|
2022-05-19 16:52:44 +02:00
|
|
|
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;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len,
|
|
|
|
offset);
|
|
|
|
|
2022-05-19 16:52:44 +02:00
|
|
|
err = available_contexts_get(NULL, &context);
|
|
|
|
if (err) {
|
|
|
|
BT_DBG("get_available_contexts returned %d", err);
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
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)
|
|
|
|
{
|
2022-05-19 16:52:44 +02:00
|
|
|
struct bt_pacs_context context;
|
|
|
|
|
2022-06-06 13:01:26 +02:00
|
|
|
#if defined(CONFIG_BT_PAC_SNK)
|
|
|
|
context.snk = sys_cpu_to_le16(CONFIG_BT_PACS_SNK_CONTEXT);
|
|
|
|
#else
|
|
|
|
context.snk = BT_AUDIO_CONTEXT_TYPE_PROHIBITED;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(CONFIG_BT_PAC_SRC)
|
|
|
|
context.src = sys_cpu_to_le16(CONFIG_BT_PACS_SRC_CONTEXT);
|
|
|
|
#else
|
|
|
|
context.src = BT_AUDIO_CONTEXT_TYPE_PROHIBITED;
|
|
|
|
#endif
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
2022-02-08 14:43:43 +01:00
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
#if defined(CONFIG_BT_PAC_SNK_LOC) || defined(CONFIG_BT_PAC_SRC_LOC)
|
2022-03-29 17:55:51 +02:00
|
|
|
static int get_pac_loc(struct bt_conn *conn, enum bt_audio_dir dir,
|
2022-02-08 14:43:43 +01:00
|
|
|
enum bt_audio_location *location)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
if (pacs_cb == NULL ||
|
|
|
|
pacs_cb->publish_location == NULL) {
|
2022-02-08 14:43:43 +01:00
|
|
|
BT_WARN("No callback for publish_location");
|
|
|
|
return -ENODATA;
|
|
|
|
}
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
err = pacs_cb->publish_location(conn, dir, location);
|
2022-02-08 14:43:43 +01:00
|
|
|
if (err != 0 || *location == 0) {
|
|
|
|
BT_DBG("err (%d) or invalid location value (%u)",
|
|
|
|
err, *location);
|
|
|
|
return -ENODATA;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2022-04-13 21:10:18 +02:00
|
|
|
#endif /* CONFIG_BT_PAC_SNK_LOC || CONFIG_BT_PAC_SRC_LOC */
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
#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);
|
|
|
|
}
|
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
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;
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
static ssize_t snk_loc_read(struct bt_conn *conn,
|
|
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
|
|
uint16_t len, uint16_t offset)
|
|
|
|
{
|
2022-02-08 14:43:43 +01:00
|
|
|
int err;
|
|
|
|
enum bt_audio_location location;
|
|
|
|
uint32_t location_32;
|
|
|
|
uint32_t location_32_le;
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len,
|
|
|
|
offset);
|
|
|
|
|
2022-03-29 18:20:10 +02:00
|
|
|
err = get_pac_loc(NULL, BT_AUDIO_DIR_SINK, &location);
|
2022-02-08 14:43:43 +01:00
|
|
|
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));
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
#if defined(CONFIG_BT_PAC_SNK_LOC_WRITEABLE)
|
2022-01-07 17:23:45 +01:00
|
|
|
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)
|
|
|
|
{
|
2022-02-08 14:43:43 +01:00
|
|
|
int err;
|
|
|
|
enum bt_audio_location location;
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
if (offset) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
|
|
}
|
|
|
|
|
2022-02-08 14:43:43 +01:00
|
|
|
if (len != sizeof(location)) {
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
if (pacs_cb == NULL ||
|
|
|
|
pacs_cb->write_location == NULL) {
|
2022-02-08 14:43:43 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
err = pacs_cb->write_location(conn, BT_AUDIO_DIR_SINK, location);
|
2022-02-08 14:43:43 +01:00
|
|
|
if (err != 0) {
|
|
|
|
BT_DBG("write_location returned %d", err);
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
2022-04-13 21:10:18 +02:00
|
|
|
#endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE */
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
static void snk_loc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
|
|
|
{
|
|
|
|
BT_DBG("attr %p value 0x%04x", attr, value);
|
|
|
|
}
|
2022-04-13 21:10:18 +02:00
|
|
|
|
|
|
|
#endif /* CONFIG_BT_PAC_SNK_LOC */
|
2022-01-07 17:23:45 +01:00
|
|
|
#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);
|
|
|
|
}
|
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
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;
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
static ssize_t src_loc_read(struct bt_conn *conn,
|
|
|
|
const struct bt_gatt_attr *attr, void *buf,
|
|
|
|
uint16_t len, uint16_t offset)
|
|
|
|
{
|
2022-02-08 14:43:43 +01:00
|
|
|
int err;
|
|
|
|
enum bt_audio_location location;
|
|
|
|
uint32_t location_32;
|
|
|
|
uint32_t location_32_le;
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_DBG("conn %p attr %p buf %p len %u offset %u", conn, attr, buf, len,
|
|
|
|
offset);
|
|
|
|
|
2022-03-29 18:20:10 +02:00
|
|
|
err = get_pac_loc(NULL, BT_AUDIO_DIR_SOURCE, &location);
|
2022-02-08 14:43:43 +01:00
|
|
|
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));
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
#if defined(BT_PAC_SRC_LOC_WRITEABLE)
|
2022-01-07 17:23:45 +01:00
|
|
|
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)
|
|
|
|
{
|
2022-02-08 14:43:43 +01:00
|
|
|
int err;
|
|
|
|
uint32_t location;
|
|
|
|
|
2022-01-07 17:23:45 +01:00
|
|
|
if (offset) {
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
|
|
|
|
}
|
|
|
|
|
2022-02-08 14:43:43 +01:00
|
|
|
if (len != sizeof(location)) {
|
2022-01-07 17:23:45 +01:00
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
|
|
|
|
}
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
if (pacs_cb == NULL ||
|
|
|
|
pacs_cb->write_location == NULL) {
|
2022-02-08 14:43:43 +01:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-04-12 16:11:18 +02:00
|
|
|
err = pacs_cb->write_location(conn, BT_AUDIO_DIR_SOURCE, location);
|
2022-02-08 14:43:43 +01:00
|
|
|
if (err != 0) {
|
|
|
|
BT_DBG("write_location returned %d", err);
|
|
|
|
return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION);
|
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
2022-04-13 21:10:18 +02:00
|
|
|
#endif /* BT_PAC_SRC_LOC_WRITEABLE */
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
static void src_loc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
|
|
|
|
{
|
|
|
|
BT_DBG("attr %p value 0x%04x", attr, value);
|
|
|
|
}
|
2022-04-13 21:10:18 +02:00
|
|
|
#endif /* CONFIG_BT_PAC_SRC_LOC */
|
2022-01-07 17:23:45 +01:00
|
|
|
#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),
|
2022-07-11 15:28:03 +02:00
|
|
|
#if defined(CONFIG_BT_PAC_SNK_LOC_WRITEABLE)
|
2022-01-07 17:23:45 +01:00
|
|
|
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,
|
2022-07-11 15:28:03 +02:00
|
|
|
snk_loc_read, snk_loc_write, NULL),
|
|
|
|
#elif defined(CONFIG_BT_PAC_SNK_LOC)
|
|
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_PACS_SNK_LOC,
|
|
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
|
|
|
|
BT_GATT_PERM_READ_ENCRYPT,
|
|
|
|
snk_loc_read, NULL, NULL),
|
2022-06-13 09:36:47 +08:00
|
|
|
#endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE */
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_GATT_CCC(snk_loc_cfg_changed,
|
|
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT),
|
|
|
|
#endif /* CONFIG_BT_PAC_SNK */
|
|
|
|
#if defined(CONFIG_BT_PAC_SRC)
|
|
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_PACS_SRC,
|
2022-01-31 11:28:59 +01:00
|
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_GATT_PERM_READ_ENCRYPT,
|
|
|
|
src_read, NULL, NULL),
|
|
|
|
BT_GATT_CCC(src_cfg_changed,
|
|
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT),
|
2022-07-11 15:28:03 +02:00
|
|
|
#if defined(CONFIG_BT_PAC_SRC_LOC_WRITEABLE)
|
2022-01-07 17:23:45 +01:00
|
|
|
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,
|
2022-07-11 15:28:03 +02:00
|
|
|
src_loc_read, src_loc_write, NULL),
|
|
|
|
#elif defined(CONFIG_BT_PAC_SRC_LOC)
|
|
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_PACS_SRC_LOC,
|
|
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
|
|
|
|
BT_GATT_PERM_READ_ENCRYPT,
|
|
|
|
src_loc_read, NULL, NULL),
|
2022-06-13 09:36:47 +08:00
|
|
|
#endif /* CONFIG_BT_PAC_SRC_LOC_WRITEABLE */
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_GATT_CCC(src_loc_cfg_changed,
|
|
|
|
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT),
|
2022-07-11 15:28:03 +02:00
|
|
|
#endif /* CONFIG_BT_PAC_SRC */
|
2022-05-19 14:44:48 +02:00
|
|
|
BT_GATT_CHARACTERISTIC(BT_UUID_PACS_AVAILABLE_CONTEXT,
|
2022-01-07 17:23:45 +01:00
|
|
|
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
|
|
|
|
BT_GATT_PERM_READ_ENCRYPT,
|
2022-05-19 16:52:44 +02:00
|
|
|
available_contexts_read, NULL, NULL),
|
|
|
|
BT_GATT_CCC(available_context_cfg_changed,
|
2022-01-07 17:23:45 +01:00
|
|
|
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)
|
|
|
|
);
|
|
|
|
|
2022-03-29 17:55:51 +02:00
|
|
|
static struct k_work_delayable *bt_pacs_get_work(enum bt_audio_dir dir)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
2022-03-29 17:55:51 +02:00
|
|
|
switch (dir) {
|
2022-01-07 17:23:45 +01:00
|
|
|
#if defined(CONFIG_BT_PAC_SNK)
|
2022-03-29 18:20:10 +02:00
|
|
|
case BT_AUDIO_DIR_SINK:
|
2022-01-07 17:23:45 +01:00
|
|
|
return &snks_work;
|
|
|
|
#endif /* CONFIG_BT_PAC_SNK */
|
|
|
|
#if defined(CONFIG_BT_PAC_SRC)
|
2022-03-29 18:20:10 +02:00
|
|
|
case BT_AUDIO_DIR_SOURCE:
|
2022-01-07 17:23:45 +01:00
|
|
|
return &srcs_work;
|
2022-06-13 09:36:47 +08:00
|
|
|
#endif /* CONFIG_BT_PAC_SRC */
|
2022-03-29 17:55:51 +02:00
|
|
|
default:
|
|
|
|
return NULL;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
#if defined(CONFIG_BT_PAC_SNK_LOC) || defined(CONFIG_BT_PAC_SRC_LOC)
|
2022-03-29 17:55:51 +02:00
|
|
|
static struct k_work_delayable *bt_pacs_get_loc_work(enum bt_audio_dir dir)
|
2022-02-08 14:43:43 +01:00
|
|
|
{
|
2022-03-29 17:55:51 +02:00
|
|
|
switch (dir) {
|
2022-02-08 14:43:43 +01:00
|
|
|
#if defined(CONFIG_BT_PAC_SNK)
|
2022-03-29 18:20:10 +02:00
|
|
|
case BT_AUDIO_DIR_SINK:
|
2022-02-08 14:43:43 +01:00
|
|
|
return &snks_loc_work;
|
|
|
|
#endif /* CONFIG_BT_PAC_SNK */
|
|
|
|
#if defined(CONFIG_BT_PAC_SRC)
|
2022-03-29 18:20:10 +02:00
|
|
|
case BT_AUDIO_DIR_SOURCE:
|
2022-02-08 14:43:43 +01:00
|
|
|
return &srcs_loc_work;
|
2022-06-13 09:36:47 +08:00
|
|
|
#endif /* CONFIG_BT_PAC_SRC */
|
2022-03-29 17:55:51 +02:00
|
|
|
default:
|
|
|
|
return NULL;
|
2022-02-08 14:43:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
static void pac_notify_loc(struct k_work *work)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
2022-04-13 21:10:18 +02:00
|
|
|
uint32_t location;
|
2022-03-29 17:55:51 +02:00
|
|
|
enum bt_audio_dir dir;
|
2022-01-31 11:28:59 +01:00
|
|
|
int err;
|
2022-01-07 17:23:45 +01:00
|
|
|
|
|
|
|
#if defined(CONFIG_BT_PAC_SNK)
|
2022-04-13 21:10:18 +02:00
|
|
|
if (work == &snks_loc_work.work) {
|
2022-03-29 18:20:10 +02:00
|
|
|
dir = BT_AUDIO_DIR_SINK;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_BT_PAC_SNK */
|
|
|
|
|
|
|
|
#if defined(CONFIG_BT_PAC_SRC)
|
2022-04-13 21:10:18 +02:00
|
|
|
if (work == &srcs_loc_work.work) {
|
2022-03-29 18:20:10 +02:00
|
|
|
dir = BT_AUDIO_DIR_SOURCE;
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_BT_PAC_SRC */
|
|
|
|
|
2022-01-31 14:38:03 +01:00
|
|
|
/* TODO: We can skip this if we are not connected to any devices */
|
2022-03-29 17:55:51 +02:00
|
|
|
err = get_pac_loc(NULL, dir, &location);
|
2022-04-13 21:10:18 +02:00
|
|
|
if (err != 0) {
|
|
|
|
BT_DBG("get_pac_loc returned %d, won't notify", err);
|
|
|
|
return;
|
|
|
|
}
|
2022-01-31 14:38:03 +01:00
|
|
|
|
2022-06-24 10:51:56 +02:00
|
|
|
if (dir == BT_AUDIO_DIR_SINK) {
|
|
|
|
err = bt_gatt_notify_uuid(NULL, BT_UUID_PACS_SNK_LOC,
|
|
|
|
pacs_svc.attrs,
|
|
|
|
&sys_cpu_to_le32(location),
|
|
|
|
sizeof(location));
|
|
|
|
} else {
|
|
|
|
err = bt_gatt_notify_uuid(NULL, BT_UUID_PACS_SRC_LOC,
|
|
|
|
pacs_svc.attrs,
|
|
|
|
&sys_cpu_to_le32(location),
|
|
|
|
sizeof(location));
|
|
|
|
}
|
|
|
|
|
2022-06-24 10:28:17 +02:00
|
|
|
if (err != 0 && err != -ENOTCONN) {
|
|
|
|
BT_WARN("PACS notify_loc failed: %d", err);
|
2022-01-31 11:28:59 +01:00
|
|
|
}
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
2022-04-13 21:10:18 +02:00
|
|
|
#endif /* CONFIG_BT_PAC_SNK_LOC || CONFIG_BT_PAC_SRC_LOC */
|
2022-01-07 17:23:45 +01:00
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
static void pac_notify(struct k_work *work)
|
2022-02-08 14:43:43 +01:00
|
|
|
{
|
2022-06-24 10:51:56 +02:00
|
|
|
int err = 0;
|
2022-02-08 14:43:43 +01:00
|
|
|
|
|
|
|
#if defined(CONFIG_BT_PAC_SNK)
|
2022-04-13 21:10:18 +02:00
|
|
|
if (work == &snks_work.work) {
|
2022-06-24 10:51:56 +02:00
|
|
|
err = bt_gatt_notify_uuid(NULL, BT_UUID_PACS_SNK,
|
|
|
|
pacs_svc.attrs, read_buf.data,
|
|
|
|
read_buf.len);
|
2022-02-08 14:43:43 +01:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_BT_PAC_SNK */
|
|
|
|
|
|
|
|
#if defined(CONFIG_BT_PAC_SRC)
|
2022-04-13 21:10:18 +02:00
|
|
|
if (work == &srcs_work.work) {
|
2022-06-24 10:51:56 +02:00
|
|
|
err = bt_gatt_notify_uuid(NULL, BT_UUID_PACS_SRC,
|
|
|
|
pacs_svc.attrs, read_buf.data,
|
|
|
|
read_buf.len);
|
2022-02-08 14:43:43 +01:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_BT_PAC_SRC */
|
|
|
|
|
2022-06-24 10:28:17 +02:00
|
|
|
if (err != 0 && err != -ENOTCONN) {
|
2022-02-08 14:43:43 +01:00
|
|
|
BT_WARN("PACS notify failed: %d", err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-29 17:55:51 +02:00
|
|
|
void bt_pacs_add_capability(enum bt_audio_dir dir)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
|
|
|
struct k_work_delayable *work;
|
|
|
|
|
2022-03-29 17:55:51 +02:00
|
|
|
work = bt_pacs_get_work(dir);
|
2022-01-07 17:23:45 +01:00
|
|
|
if (!work) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize handler if it hasn't been initialized */
|
|
|
|
if (!work->work.handler) {
|
2022-01-31 11:28:59 +01:00
|
|
|
k_work_init_delayable(work, pac_notify);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-01-31 11:28:59 +01:00
|
|
|
k_work_reschedule(work, PAC_NOTIFY_TIMEOUT);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
|
|
|
|
2022-03-29 17:55:51 +02:00
|
|
|
void bt_pacs_remove_capability(enum bt_audio_dir dir)
|
2022-01-07 17:23:45 +01:00
|
|
|
{
|
|
|
|
struct k_work_delayable *work;
|
|
|
|
|
2022-03-29 17:55:51 +02:00
|
|
|
work = bt_pacs_get_work(dir);
|
2022-01-07 17:23:45 +01:00
|
|
|
if (!work) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-31 11:28:59 +01:00
|
|
|
k_work_reschedule(work, PAC_NOTIFY_TIMEOUT);
|
2022-01-07 17:23:45 +01:00
|
|
|
}
|
2022-02-08 14:43:43 +01:00
|
|
|
|
2022-04-13 21:10:18 +02:00
|
|
|
#if defined(CONFIG_BT_PAC_SNK_LOC) || defined(CONFIG_BT_PAC_SRC_LOC)
|
2022-04-12 16:11:18 +02:00
|
|
|
/******* PUBLIC API *******/
|
|
|
|
int bt_audio_pacs_location_changed(enum bt_audio_dir dir)
|
2022-02-08 14:43:43 +01:00
|
|
|
{
|
|
|
|
struct k_work_delayable *work;
|
|
|
|
|
2022-03-29 17:55:51 +02:00
|
|
|
work = bt_pacs_get_loc_work(dir);
|
2022-02-08 14:43:43 +01:00
|
|
|
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;
|
|
|
|
}
|
2022-04-13 21:10:18 +02:00
|
|
|
#endif /* CONFIG_BT_PAC_SNK_LOC || CONFIG_BT_PAC_SRC_LOC */
|
2022-05-19 16:52:44 +02:00
|
|
|
|
|
|
|
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));
|
2022-06-24 10:28:17 +02:00
|
|
|
if (err != 0 && err != -ENOTCONN) {
|
2022-05-19 16:52:44 +02:00
|
|
|
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;
|
|
|
|
}
|
2022-04-12 16:11:18 +02:00
|
|
|
|
|
|
|
int bt_audio_pacs_register_cb(const struct bt_audio_pacs_cb *cb)
|
|
|
|
{
|
|
|
|
CHECKIF(cb == NULL) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pacs_cb != NULL) {
|
|
|
|
return -EALREADY;
|
|
|
|
}
|
|
|
|
|
|
|
|
pacs_cb = cb;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2022-04-05 11:50:38 +02:00
|
|
|
|
|
|
|
bool bt_pacs_context_available(enum bt_audio_dir dir, uint16_t context)
|
|
|
|
{
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PAC_SRC) && dir == BT_AUDIO_DIR_SOURCE) {
|
|
|
|
return (context & available_context.src) == context;
|
|
|
|
} else if (IS_ENABLED(CONFIG_BT_PAC_SNK) && dir == BT_AUDIO_DIR_SINK) {
|
|
|
|
return (context & available_context.snk) == context;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|