blob: ffd40ea091e8f2baca30fd3fece65fcccf55e9f8 [file] [log] [blame]
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/bluetooth/audio/tmap.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/types.h>
#include "audio_internal.h"
LOG_MODULE_REGISTER(bt_tmap, CONFIG_BT_TMAP_LOG_LEVEL);
/* Hex value if all TMAP role bits are set */
#define TMAP_ALL_ROLES 0x3F
static uint16_t tmap_role;
static const struct bt_tmap_cb *cb;
static bool tmas_found;
static struct bt_uuid_16 uuid[CONFIG_BT_MAX_CONN] = {BT_UUID_INIT_16(0)};
static struct bt_gatt_discover_params discover_params[CONFIG_BT_MAX_CONN];
static struct bt_gatt_read_params read_params[CONFIG_BT_MAX_CONN];
uint8_t tmap_char_read(struct bt_conn *conn, uint8_t err,
struct bt_gatt_read_params *params,
const void *data, uint16_t length)
{
uint16_t peer_role;
/* Check read request result */
if (err != BT_ATT_ERR_SUCCESS) {
if (cb->discovery_complete) {
cb->discovery_complete(0, conn, err);
}
return BT_GATT_ITER_STOP;
}
/* Check data length */
if (length != sizeof(peer_role)) {
if (cb->discovery_complete) {
cb->discovery_complete(0, conn, BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
return BT_GATT_ITER_STOP;
}
/* Extract the TMAP role of the peer and inform application fo the value found */
peer_role = sys_get_le16(data);
if ((peer_role > 0U) && (peer_role <= TMAP_ALL_ROLES)) {
if (cb->discovery_complete) {
cb->discovery_complete((enum bt_tmap_role)peer_role, conn, 0);
}
} else {
if (cb->discovery_complete) {
cb->discovery_complete(0, conn, BT_ATT_ERR_VALUE_NOT_ALLOWED);
}
}
return BT_GATT_ITER_STOP;
}
static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params)
{
int err;
uint8_t conn_id = bt_conn_index(conn);
if (!attr) {
(void)memset(params, 0, sizeof(*params));
if (!tmas_found) {
/* TMAS not found on peer */
if (cb->discovery_complete) {
cb->discovery_complete(0, conn, BT_ATT_ERR_ATTRIBUTE_NOT_FOUND);
}
}
tmas_found = false;
return BT_GATT_ITER_STOP;
}
if (!bt_uuid_cmp(discover_params[conn_id].uuid, BT_UUID_TMAS)) {
LOG_DBG("Discovered TMAS\n");
tmas_found = true;
memcpy(&uuid[conn_id], BT_UUID_GATT_TMAPR, sizeof(uuid[conn_id]));
discover_params[conn_id].uuid = &uuid[conn_id].uuid;
discover_params[conn_id].start_handle = attr->handle + 1;
discover_params[conn_id].type = BT_GATT_DISCOVER_CHARACTERISTIC;
/* Discovered TMAS - Search for TMAP Role characteristic */
err = bt_gatt_discover(conn, &discover_params[conn_id]);
if (err) {
LOG_DBG("Discover failed (err %d)\n", err);
}
} else if (!bt_uuid_cmp(discover_params[conn_id].uuid, BT_UUID_GATT_TMAPR)) {
/* Use 0 for now, will expand later */
read_params[conn_id].func = tmap_char_read;
read_params[conn_id].handle_count = 1u;
read_params[conn_id].single.handle = bt_gatt_attr_value_handle(attr);
read_params[conn_id].single.offset = 0;
/* Discovered TMAP Role characteristic - read value */
err = bt_gatt_read(conn, &read_params[0]);
if (err != 0) {
LOG_DBG("Could not read peer TMAP Role");
}
} else {
return BT_GATT_ITER_CONTINUE;
}
return BT_GATT_ITER_STOP;
}
static ssize_t read_role(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint16_t role;
role = sys_cpu_to_le16(tmap_role);
LOG_DBG("TMAP: role 0x%04X", role);
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&role, sizeof(role));
}
/* Telephony and Media Audio Service attributes */
#define BT_TMAS_SERVICE_DEFINITION \
BT_GATT_PRIMARY_SERVICE(BT_UUID_TMAS), \
BT_GATT_CHARACTERISTIC(BT_UUID_GATT_TMAPR, \
BT_GATT_CHRC_READ, \
BT_GATT_PERM_READ, \
read_role, NULL, NULL)
static struct bt_gatt_attr svc_attrs[] = { BT_TMAS_SERVICE_DEFINITION };
static struct bt_gatt_service tmas;
int bt_tmap_register(enum bt_tmap_role role)
{
int err;
tmas = (struct bt_gatt_service)BT_GATT_SERVICE(svc_attrs);
err = bt_gatt_service_register(&tmas);
if (err) {
LOG_DBG("Could not register the TMAS service");
return -ENOEXEC;
}
tmap_role = role;
tmas_found = false;
return 0;
}
int bt_tmap_discover(struct bt_conn *conn, const struct bt_tmap_cb *tmap_cb)
{
int err = 0;
uint8_t conn_id = bt_conn_index(conn);
cb = tmap_cb;
memcpy(&uuid[conn_id], BT_UUID_TMAS, sizeof(uuid[conn_id]));
discover_params[conn_id].uuid = &uuid[conn_id].uuid;
discover_params[conn_id].func = discover_func;
discover_params[conn_id].start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params[conn_id].end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params[conn_id].type = BT_GATT_DISCOVER_PRIMARY;
err = bt_gatt_discover(conn, &discover_params[conn_id]);
return err;
}
void bt_tmap_set_role(enum bt_tmap_role role)
{
tmap_role = role;
}