zephyr/subsys/bluetooth/audio/pacs.c
Emil Gydesen 18466530ab Bluetooth: PACS: Refactor PAC location read/write
Refactor the PAC location read and write. Instead
of storing the location in the service, the
location is now stored in the application, and
is retrieved by the service via callbacks.

Similarly, if a client writes the location, this
request is being sent to the application.

Signed-off-by: Emil Gydesen <emil.gydesen@nordicsemi.no>
2022-03-11 11:36:19 -08:00

577 lines
15 KiB
C

/* @file
* @brief Bluetooth PACS
*/
/*
* Copyright (c) 2020 Intel Corporation
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <sys/byteorder.h>
#include <device.h>
#include <init.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/conn.h>
#include <bluetooth/gatt.h>
#include <bluetooth/audio/audio.h>
#include <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)
#if defined(CONFIG_BT_PAC_SNK) || defined(CONFIG_BT_PAC_SRC)
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, uint8_t type,
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, type,
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)
{
uint8_t type;
if (!bt_uuid_cmp(attr->uuid, BT_UUID_PACS_SNK)) {
type = BT_AUDIO_SINK;
} else {
type = BT_AUDIO_SOURCE;
}
get_pac_records(conn, type, &read_buf);
return bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data,
read_buf.len);
}
static void context_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
BT_DBG("attr %p value 0x%04x", attr, value);
}
static ssize_t 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 = {
/* TODO: This should reflect the ongoing channel contexts */
.snk = 0x0001,
.src = 0x0001,
};
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));
}
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 defined(CONFIG_BT_PAC_SNK)
.snk = sys_cpu_to_le16(CONFIG_BT_PACS_SNK_CONTEXT),
#endif
#if defined(CONFIG_BT_PAC_SRC)
.src = sys_cpu_to_le16(CONFIG_BT_PACS_SRC_CONTEXT),
#endif
};
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));
}
static int get_pac_loc(struct bt_conn *conn, enum bt_audio_pac_type type,
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, type, 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 || CONFIG_BT_PAC_SRC */
#if defined(CONFIG_BT_PAC_SNK)
static struct k_work_delayable snks_work;
static struct k_work_delayable snks_loc_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 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_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));
}
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_SINK, location);
if (err != 0) {
BT_DBG("write_location returned %d", err);
return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION);
}
return len;
}
static void snk_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
BT_DBG("attr %p value 0x%04x", attr, value);
}
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 */
#if defined(CONFIG_BT_PAC_SRC)
static struct k_work_delayable srcs_work;
static struct k_work_delayable srcs_loc_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 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_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));
}
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_SOURCE,
location);
if (err != 0) {
BT_DBG("write_location returned %d", err);
return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION);
}
return len;
}
static void src_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
BT_DBG("attr %p value 0x%04x", attr, value);
}
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 */
#if defined(CONFIG_BT_PAC_SNK) || defined(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),
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, snk_loc_write, NULL),
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,
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),
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, src_loc_write, NULL),
BT_GATT_CCC(src_loc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT),
#endif /* CONFIG_BT_PAC_SNK */
BT_GATT_CHARACTERISTIC(BT_UUID_PACS_CONTEXT,
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ_ENCRYPT,
context_read, NULL, NULL),
BT_GATT_CCC(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)
);
#endif /* CONFIG_BT_PAC_SNK || CONFIG_BT_PAC_SRC */
static struct k_work_delayable *bt_pacs_get_work(uint8_t type)
{
switch (type) {
#if defined(CONFIG_BT_PAC_SNK)
case BT_AUDIO_SINK:
return &snks_work;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
case BT_AUDIO_SOURCE:
return &srcs_work;
#endif /* CONFIG_BT_PAC_SNK */
}
return NULL;
}
static struct k_work_delayable *bt_pacs_get_loc_work(uint8_t type)
{
switch (type) {
#if defined(CONFIG_BT_PAC_SNK)
case BT_AUDIO_SINK:
return &snks_loc_work;
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
case BT_AUDIO_SOURCE:
return &srcs_loc_work;
#endif /* CONFIG_BT_PAC_SNK */
}
return NULL;
}
static void pac_notify(struct k_work *work)
{
#if defined(CONFIG_BT_PAC_SNK) || defined(CONFIG_BT_PAC_SRC)
struct bt_uuid *uuid;
uint8_t type;
int err;
#if defined(CONFIG_BT_PAC_SNK)
if (work == &snks_work.work) {
type = BT_AUDIO_SINK;
uuid = BT_UUID_PACS_SNK;
}
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
if (work == &srcs_work.work) {
type = BT_AUDIO_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, type, &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);
}
#endif /* CONFIG_BT_PAC_SNK || CONFIG_BT_PAC_SRC */
}
static void pac_notify_loc(struct k_work *work)
{
#if defined(CONFIG_BT_PAC_SNK) || defined(CONFIG_BT_PAC_SRC)
uint32_t location;
struct bt_uuid *uuid;
enum bt_audio_pac_type type;
int err;
#if defined(CONFIG_BT_PAC_SNK)
if (work == &snks_loc_work.work) {
type = BT_AUDIO_SINK;
uuid = BT_UUID_PACS_SNK_LOC;
}
#endif /* CONFIG_BT_PAC_SNK */
#if defined(CONFIG_BT_PAC_SRC)
if (work == &srcs_loc_work.work) {
type = BT_AUDIO_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, type, &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 || CONFIG_BT_PAC_SRC */
}
void bt_pacs_add_capability(uint8_t type)
{
struct k_work_delayable *work;
work = bt_pacs_get_work(type);
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(uint8_t type)
{
struct k_work_delayable *work;
work = bt_pacs_get_work(type);
if (!work) {
return;
}
k_work_reschedule(work, PAC_NOTIFY_TIMEOUT);
}
int bt_pacs_location_changed(enum bt_audio_pac_type type)
{
struct k_work_delayable *work;
work = bt_pacs_get_loc_work(type);
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;
}