Added option to set the ASE count through the bap API, making ASE configuration runtime available. The upper limit of ASEs are still bound by the Kconfig options set for ASEs. Signed-off-by: Fredrik Danebjer <frdn@demant.com>
429 lines
12 KiB
C
429 lines
12 KiB
C
/*
|
|
* Copyright (c) 2023 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <zephyr/autoconf.h>
|
|
#include <zephyr/bluetooth/audio/gmap.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/gatt.h>
|
|
#include <zephyr/bluetooth/uuid.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys/check.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/sys/util_macro.h>
|
|
|
|
#include "audio_internal.h"
|
|
|
|
LOG_MODULE_REGISTER(bt_gmap_server, CONFIG_BT_GMAP_LOG_LEVEL);
|
|
|
|
#define BT_GMAP_ROLE_MASK \
|
|
(BT_GMAP_ROLE_UGG | BT_GMAP_ROLE_UGT | BT_GMAP_ROLE_BGS | BT_GMAP_ROLE_BGR)
|
|
|
|
static uint8_t gmap_role;
|
|
static struct bt_gmap_feat gmap_features;
|
|
|
|
static ssize_t read_gmap_role(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
LOG_DBG("role 0x%02X", gmap_role);
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &gmap_role, sizeof(gmap_role));
|
|
}
|
|
|
|
#if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
|
|
static ssize_t read_gmap_ugg_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
const uint8_t feat = (uint8_t)gmap_features.ugg_feat;
|
|
|
|
LOG_DBG("feat 0x%02X", feat);
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
|
|
}
|
|
|
|
static const struct bt_gatt_attr ugg_feat_chrc[] = {
|
|
BT_AUDIO_CHRC(BT_UUID_GMAP_UGG_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
|
|
read_gmap_ugg_feat, NULL, NULL),
|
|
};
|
|
#endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
|
|
|
|
#if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
|
|
static ssize_t read_gmap_ugt_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
const uint8_t feat = (uint8_t)gmap_features.ugt_feat;
|
|
|
|
LOG_DBG("feat 0x%02X", feat);
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
|
|
}
|
|
|
|
static const struct bt_gatt_attr ugt_feat_chrc[] = {
|
|
BT_AUDIO_CHRC(BT_UUID_GMAP_UGT_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
|
|
read_gmap_ugt_feat, NULL, NULL),
|
|
};
|
|
|
|
#endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
|
|
|
|
#if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
|
|
static ssize_t read_gmap_bgs_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
const uint8_t feat = (uint8_t)gmap_features.bgs_feat;
|
|
|
|
LOG_DBG("feat 0x%02X", feat);
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
|
|
}
|
|
|
|
static const struct bt_gatt_attr bgs_feat_chrc[] = {
|
|
BT_AUDIO_CHRC(BT_UUID_GMAP_BGS_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
|
|
read_gmap_bgs_feat, NULL, NULL),
|
|
};
|
|
#endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
|
|
|
|
#if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
|
|
static ssize_t read_gmap_bgr_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
|
|
uint16_t len, uint16_t offset)
|
|
{
|
|
const uint8_t feat = (uint8_t)gmap_features.bgr_feat;
|
|
|
|
LOG_DBG("feat 0x%02X", feat);
|
|
|
|
return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat));
|
|
}
|
|
|
|
static const struct bt_gatt_attr bgr_feat_chrc[] = {
|
|
BT_AUDIO_CHRC(BT_UUID_GMAP_BGR_FEAT, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
|
|
read_gmap_bgr_feat, NULL, NULL),
|
|
};
|
|
#endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
|
|
|
|
/* There are 4 optional characteristics - Use a dummy definition to allocate memory and then add the
|
|
* characteristics at will when registering or modifying the role(s)
|
|
*/
|
|
#define GMAS_DUMMY_CHRC BT_AUDIO_CHRC(0, 0, 0, NULL, NULL, NULL)
|
|
|
|
/* Gaming Audio Service attributes */
|
|
static struct bt_gatt_attr svc_attrs[] = {
|
|
BT_GATT_PRIMARY_SERVICE(BT_UUID_GMAS),
|
|
BT_AUDIO_CHRC(BT_UUID_GMAP_ROLE, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT,
|
|
read_gmap_role, NULL, NULL),
|
|
#if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
|
|
GMAS_DUMMY_CHRC,
|
|
#endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
|
|
#if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
|
|
GMAS_DUMMY_CHRC,
|
|
#endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
|
|
#if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
|
|
GMAS_DUMMY_CHRC,
|
|
#endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
|
|
#if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
|
|
GMAS_DUMMY_CHRC,
|
|
#endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
|
|
};
|
|
static struct bt_gatt_service gmas;
|
|
|
|
static bool valid_gmap_role(enum bt_gmap_role role)
|
|
{
|
|
if (role == 0 || (role & BT_GMAP_ROLE_MASK) != role) {
|
|
LOG_DBG("Invalid role %d", role);
|
|
}
|
|
|
|
if ((role & BT_GMAP_ROLE_UGG) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_UGG_SUPPORTED)) {
|
|
LOG_DBG("Device does not support the UGG role");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((role & BT_GMAP_ROLE_UGT) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_UGT_SUPPORTED)) {
|
|
LOG_DBG("Device does not support the UGT role");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((role & BT_GMAP_ROLE_BGS) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGS_SUPPORTED)) {
|
|
LOG_DBG("Device does not support the BGS role");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((role & BT_GMAP_ROLE_BGR) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGR_SUPPORTED)) {
|
|
LOG_DBG("Device does not support the BGR role");
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool valid_gmap_features(enum bt_gmap_role role, struct bt_gmap_feat features)
|
|
{
|
|
/* Guard with BT_GMAP_UGG_SUPPORTED as the Kconfigs may not be available without it*/
|
|
#if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
|
|
if ((role & BT_GMAP_ROLE_UGG) != 0) {
|
|
enum bt_gmap_ugg_feat ugg_feat = features.ugg_feat;
|
|
|
|
if ((ugg_feat & BT_GMAP_UGG_FEAT_MULTIPLEX) != 0 &&
|
|
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) {
|
|
LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_MULTIPLEX with "
|
|
"CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((ugg_feat & BT_GMAP_UGG_FEAT_96KBPS_SOURCE) != 0 &&
|
|
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) {
|
|
LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_96KBPS_SOURCE with "
|
|
"CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((ugg_feat & BT_GMAP_UGG_FEAT_MULTISINK) != 0 &&
|
|
(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT < 2 ||
|
|
CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT < 2)) {
|
|
LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_MULTISINK with "
|
|
"CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT (%d) or "
|
|
"CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT (%d) < 2",
|
|
CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT,
|
|
CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */
|
|
|
|
#if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
|
|
if ((role & BT_GMAP_ROLE_UGT) != 0) {
|
|
enum bt_gmap_ugt_feat ugt_feat = features.ugt_feat;
|
|
enum bt_gmap_bgr_feat bgr_feat = features.bgr_feat;
|
|
|
|
if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) == 0 &&
|
|
(ugt_feat & BT_GMAP_UGT_FEAT_SINK) == 0) {
|
|
LOG_DBG("Device shall support either BT_GMAP_UGT_FEAT_SOURCE or "
|
|
"BT_GMAP_UGT_FEAT_SINK");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) == 0 &&
|
|
((ugt_feat & BT_GMAP_UGT_FEAT_80KBPS_SOURCE) != 0 ||
|
|
(ugt_feat & BT_GMAP_UGT_FEAT_MULTISOURCE) != 0)) {
|
|
LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_SOURCE if "
|
|
"BT_GMAP_UGT_FEAT_80KBPS_SOURCE or BT_GMAP_UGT_FEAT_MULTISOURCE is "
|
|
"supported");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) != 0 &&
|
|
CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == 0) {
|
|
LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_SOURCE with "
|
|
"CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == 0");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((ugt_feat & BT_GMAP_UGT_FEAT_MULTISOURCE) != 0 &&
|
|
(CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT < 2 || CONFIG_BT_ASCS_MAX_ACTIVE_ASES < 2)) {
|
|
LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_MULTISOURCE with "
|
|
"CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT (%d) or "
|
|
"CONFIG_BT_ASCS_MAX_ACTIVE_ASES (%d) < 2",
|
|
CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT, CONFIG_BT_ASCS_MAX_ACTIVE_ASES);
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((ugt_feat & BT_GMAP_UGT_FEAT_SINK) != 0
|
|
&& CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT == 0) {
|
|
LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_SINK with "
|
|
"CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT == 0");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((ugt_feat & BT_GMAP_UGT_FEAT_SINK) == 0 &&
|
|
((ugt_feat & BT_GMAP_UGT_FEAT_64KBPS_SINK) != 0 ||
|
|
(ugt_feat & BT_GMAP_UGT_FEAT_MULTISINK) != 0 ||
|
|
(ugt_feat & BT_GMAP_UGT_FEAT_MULTIPLEX) != 0)) {
|
|
LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_SINK if "
|
|
"BT_GMAP_UGT_FEAT_64KBPS_SINK, BT_GMAP_UGT_FEAT_MULTISINK or "
|
|
"BT_GMAP_UGT_FEAT_MULTIPLEX is supported");
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((ugt_feat & BT_GMAP_UGT_FEAT_MULTISINK) != 0 &&
|
|
(CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT < 2 || CONFIG_BT_ASCS_MAX_ACTIVE_ASES < 2)) {
|
|
LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_MULTISINK with "
|
|
"CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT (%d) or "
|
|
"CONFIG_BT_ASCS_MAX_ACTIVE_ASES (%d) < 2",
|
|
CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT, CONFIG_BT_ASCS_MAX_ACTIVE_ASES);
|
|
|
|
return false;
|
|
}
|
|
|
|
/* If the device supports both the UGT and BGT roles, then it needs have the same
|
|
* support for multiplexing for both roles
|
|
*/
|
|
if ((role & BT_GMAP_ROLE_BGR) != 0 && !IS_ENABLED(CONFIG_BT_GMAP_BGR_SUPPORTED) &&
|
|
(ugt_feat & BT_GMAP_UGT_FEAT_MULTIPLEX) !=
|
|
(bgr_feat & BT_GMAP_BGR_FEAT_MULTIPLEX)) {
|
|
LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_MULTIPLEX if "
|
|
"BT_GMAP_BGR_FEAT_MULTIPLEX is supported, and vice versa");
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
|
|
|
|
#if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
|
|
if ((role & BT_GMAP_ROLE_BGR) != 0) {
|
|
enum bt_gmap_bgr_feat bgr_feat = features.bgr_feat;
|
|
|
|
if ((bgr_feat & BT_GMAP_BGR_FEAT_MULTISINK) != 0 &&
|
|
CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT < 2) {
|
|
LOG_DBG("Cannot support BT_GMAP_BGR_FEAT_MULTISINK with "
|
|
"CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT (%d) < 2",
|
|
CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
|
|
|
|
/* If the roles are not supported, then the feature characteristics are not instantiated and
|
|
* the feature values do not need to be checked, as they will never be read (thus ignore by
|
|
* the stack)
|
|
*/
|
|
|
|
return true;
|
|
}
|
|
|
|
static void update_service(enum bt_gmap_role role)
|
|
{
|
|
gmas.attrs = svc_attrs;
|
|
gmas.attr_count = 3; /* service + 2 attributes for BT_UUID_GMAP_ROLE */
|
|
|
|
/* Add characteristics based on the role selected and what is supported */
|
|
#if defined(CONFIG_BT_GMAP_UGG_SUPPORTED)
|
|
if (role & BT_GMAP_ROLE_UGG) {
|
|
memcpy(&gmas.attrs[gmas.attr_count], ugg_feat_chrc, sizeof(ugg_feat_chrc));
|
|
gmas.attr_count += ARRAY_SIZE(ugg_feat_chrc);
|
|
}
|
|
#endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */
|
|
|
|
#if defined(CONFIG_BT_GMAP_UGT_SUPPORTED)
|
|
if (role & BT_GMAP_ROLE_UGT) {
|
|
memcpy(&gmas.attrs[gmas.attr_count], ugt_feat_chrc, sizeof(ugt_feat_chrc));
|
|
gmas.attr_count += ARRAY_SIZE(ugt_feat_chrc);
|
|
}
|
|
#endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */
|
|
|
|
#if defined(CONFIG_BT_GMAP_BGS_SUPPORTED)
|
|
if (role & BT_GMAP_ROLE_BGS) {
|
|
memcpy(&gmas.attrs[gmas.attr_count], bgs_feat_chrc, sizeof(bgs_feat_chrc));
|
|
gmas.attr_count += ARRAY_SIZE(bgs_feat_chrc);
|
|
}
|
|
#endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */
|
|
|
|
#if defined(CONFIG_BT_GMAP_BGR_SUPPORTED)
|
|
if (role & BT_GMAP_ROLE_BGR) {
|
|
memcpy(&gmas.attrs[gmas.attr_count], bgr_feat_chrc, sizeof(bgr_feat_chrc));
|
|
gmas.attr_count += ARRAY_SIZE(bgr_feat_chrc);
|
|
}
|
|
#endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */
|
|
}
|
|
|
|
int bt_gmap_register(enum bt_gmap_role role, struct bt_gmap_feat features)
|
|
{
|
|
int err;
|
|
|
|
CHECKIF(!valid_gmap_role(role)) {
|
|
LOG_DBG("Invalid role: %d", role);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(!valid_gmap_features(role, features)) {
|
|
LOG_DBG("Invalid features");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
update_service(role);
|
|
|
|
err = bt_gatt_service_register(&gmas);
|
|
if (err) {
|
|
LOG_DBG("Could not register the GMAS service");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
gmap_role = role;
|
|
gmap_features = features;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_gmap_set_role(enum bt_gmap_role role, struct bt_gmap_feat features)
|
|
{
|
|
int err;
|
|
|
|
if (gmap_role == 0) { /* not registered if this is 0 */
|
|
LOG_DBG("GMAP not registered");
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
CHECKIF(!valid_gmap_role(role)) {
|
|
LOG_DBG("Invalid role: %d", role);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
CHECKIF(!valid_gmap_features(role, features)) {
|
|
LOG_DBG("Invalid features");
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (gmap_role == role) {
|
|
LOG_DBG("No role change");
|
|
|
|
if (memcmp(&gmap_features, &features, sizeof(gmap_features)) == 0) {
|
|
LOG_DBG("No feature change");
|
|
|
|
return -EALREADY;
|
|
}
|
|
|
|
gmap_features = features;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Re-register the service to trigger a db_changed() if the roles changed */
|
|
err = bt_gatt_service_unregister(&gmas);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to unregister service: %d", err);
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
err = bt_gmap_register(role, gmap_features);
|
|
if (err != 0) {
|
|
LOG_DBG("Failed to update GMAS: %d", err);
|
|
|
|
return -ECANCELED;
|
|
}
|
|
|
|
return 0;
|
|
}
|