blob: c7f74956e43ce0843a9ed38ac9e2115fb39bcbb5 [file]
// SPDX-License-Identifier: LGPL-2.1-or-later
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2022 Intel Corporation. All rights reserved.
*
*/
#define _GNU_SOURCE
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include "bluetooth/bluetooth.h"
#include "bluetooth/uuid.h"
#include "src/shared/queue.h"
#include "src/shared/util.h"
#include "src/shared/timeout.h"
#include "src/shared/att.h"
#include "src/shared/gatt-db.h"
#include "src/shared/gatt-server.h"
#include "src/shared/gatt-client.h"
#include "src/shared/vcp.h"
#define DBG(_vcp, fmt, arg...) \
vcp_debug(_vcp, "%s:%s() " fmt, __FILE__, __func__, ## arg)
#define VCP_STEP_SIZE 1
#define VCP_CLIENT_OP_TIMEOUT 2000
#define VOCS_VOL_OFFSET_UPPER_LIMIT 255
#define VOCS_VOL_OFFSET_LOWER_LIMIT -255
/* Application Error Code */
#define BT_ATT_ERROR_INVALID_CHANGE_COUNTER 0x80
#define BT_ATT_ERROR_OPCODE_NOT_SUPPORTED 0x81
#define BT_ATT_ERROR_VALUE_OUT_OF_RANGE 0x82
#define BT_ATT_AICS_ERROR_VALUE_OUT_OF_RANGE 0x83
#define BT_ATT_AICS_ERROR_MUTE_DISABLED 0x82
#define BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED 0x84
#define BT_VCP_NA BIT(0)
#define BT_VCP_FRONT_LEFT BIT(1)
#define BT_VCP_FRONT_RIGHT BIT(2)
#define BT_VCP_FRONT_CENTER BIT(3)
#define BT_VCP_LOW_FRQ_EFF_1 BIT(4)
#define BT_VCP_BACK_LEFT BIT(5)
#define BT_VCP_BACK_RIGHT BIT(6)
#define BT_VCP_FRONT_LEFT_CENTER BIT(7)
#define BT_VCP_FRONT_RIGHT_CENTER BIT(8)
#define BT_VCP_BACK_CENTER BIT(9)
#define BT_VCP_LOW_FRQ_EFF_2 BIT(10)
#define BT_VCP_SIDE_LEFT BIT(11)
#define BT_VCP_SIDE_RIGHT BIT(12)
#define BT_VCP_TOP_FRONT_LEFT BIT(13)
#define BT_VCP_TOP_FRONT_RIGHT BIT(14)
#define BT_VCP_TOP_FRONT_CENTER BIT(15)
#define BT_VCP_TOP_CENTER BIT(16)
#define BT_VCP_TOP_BACK_LEFT BIT(17)
#define BT_VCP_TOP_BACK_RIGHT BIT(18)
#define BT_VCP_TOP_SIDE_LEFT BIT(19)
#define BT_VCP_TOP_SIDE_RIGHT BIT(20)
#define BT_VCP_TOP_BACK_CENTER BIT(21)
#define BT_VCP_BOTTOM_FRONT_CENTER BIT(22)
#define BT_VCP_BOTTOM_FRONT_LEFT BIT(23)
#define BT_VCP_BOTTOM_FRONT_RIGHT BIT(24)
#define BT_VCP_FRONT_LEFT_WIDE BIT(25)
#define BT_VCP_FRONT_RIGHT_WIDE BIT(26)
#define BT_VCP_LEFT_SURROUND BIT(27)
#define BT_VCP_RIGHT_SURROUND BIT(28)
#define VCS_TOTAL_NUM_HANDLES 11
#define AICS_TOTAL_NUM_HANDLES 16
/* AICS Audio Input Type Values */
#define AICS_AUD_IP_TYPE_UNSPECIFIED 0x00
#define AICS_AUD_IP_TYPE_BLUETOOTH 0x01
#define AICS_AUD_IP_TYPE_MICROPHONE 0x02
#define AICS_AUD_IP_TYPE_ANALOG 0x03
#define AICS_AUD_IP_TYPE_DIGITAL 0x04
#define AICS_AUD_IP_TYPE_RADIO 0x05
#define AICS_AUD_IP_TYPE_STREAMING 0x06
#define AICS_AUD_IP_TYPE_AMBIENT 0x07
/* AICS Audio Input Status Values */
#define AICS_AUD_IP_STATUS_INACTIVE 0x00
#define AICS_AUD_IP_STATUS_ACTIVE 0x01
/* AICS Audio Input Control Point Opcodes */
#define BT_AICS_SET_GAIN_SETTING 0x01
#define BT_AICS_UNMUTE 0x02
#define BT_AICS_MUTE 0x03
#define BT_AICS_SET_MANUAL_GAIN_MODE 0x04
#define BT_AICS_SET_AUTO_GAIN_MODE 0x05
/* AICS Gain Mode Field Value */
#define AICS_GAIN_MODE_MANUAL_ONLY 0x00
#define AICS_GAIN_MODE_AUTO_ONLY 0x01
#define AICS_GAIN_MODE_MANUAL 0x02
#define AICS_GAIN_MODE_AUTO 0x03
/* AICS Mute Field Values */
#define AICS_NOT_MUTED 0x00
#define AICS_MUTED 0x01
#define AICS_DISABLED 0x02
#define AICS_GAIN_SETTING_UNITS 1
#define AICS_GAIN_SETTING_MAX_VALUE 127
#define AICS_GAIN_SETTING_MIN_VALUE -128
#define AICS_GAIN_SETTING_DEFAULT_VALUE 88
struct bt_vcp_db {
struct gatt_db *db;
struct bt_vcs *vcs;
struct bt_vocs *vocs;
struct bt_aics *aics;
};
typedef void (*vcp_func_t)(struct bt_vcp *vcp, bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data);
struct bt_vcp_pending {
unsigned int id;
struct bt_vcp *vcp;
vcp_func_t func;
void *user_data;
};
struct bt_vcs_param {
uint8_t op;
uint8_t change_counter;
} __packed;
struct bt_vocs_param {
uint8_t op;
uint8_t change_counter;
} __packed;
struct bt_vcs_ab_vol {
uint8_t change_counter;
uint8_t vol_set;
} __packed;
struct bt_vcs_client_ab_vol {
uint8_t op;
uint8_t change_counter;
uint8_t vol_set;
} __packed;
struct bt_vocs_set_vol_off {
uint8_t change_counter;
int16_t set_vol_offset;
} __packed;
struct bt_vcp_cb {
unsigned int id;
bt_vcp_func_t attached;
bt_vcp_func_t detached;
void *user_data;
};
typedef void (*vcp_notify_t)(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data);
struct bt_vcp_notify {
unsigned int id;
struct bt_vcp *vcp;
vcp_notify_t func;
void *user_data;
};
struct bt_vcp_client_op {
uint8_t volume;
bool resend;
bool wait_reply;
bool wait_notify;
unsigned int timeout_id;
};
struct bt_vcp {
int ref_count;
struct bt_vcp_db *ldb;
struct bt_vcp_db *rdb;
struct bt_gatt_client *client;
struct bt_att *att;
unsigned int vstate_id;
unsigned int vflag_id;
unsigned int state_id;
unsigned int audio_loc_id;
unsigned int ao_dec_id;
unsigned int aics_ip_state_id;
unsigned int aics_ip_status_id;
unsigned int aics_ip_descr_id;
unsigned int idle_id;
bt_vcp_func_t ready_func;
void *ready_data;
struct queue *notify;
struct queue *pending;
bt_vcp_debug_func_t debug_func;
bt_vcp_destroy_func_t debug_destroy;
bt_vcp_volume_func_t volume_changed;
uint8_t volume;
uint8_t volume_counter;
struct bt_vcp_client_op pending_op;
void *debug_data;
void *user_data;
};
#define RESET_VOLUME_SETTING 0x00
#define USERSET_VOLUME_SETTING 0x01
/* Contains local bt_vcp_db */
struct vol_state {
uint8_t vol_set;
uint8_t mute;
uint8_t counter;
} __packed;
struct bt_vcs {
struct bt_vcp_db *vdb;
struct vol_state *vstate;
uint8_t vol_flag;
struct gatt_db_attribute *service;
struct gatt_db_attribute *vs;
struct gatt_db_attribute *vs_ccc;
struct gatt_db_attribute *vol_cp;
struct gatt_db_attribute *vf;
struct gatt_db_attribute *vf_ccc;
};
/* Contains local bt_vcp_db */
struct vol_offset_state {
int16_t vol_offset;
uint8_t counter;
} __packed;
struct bt_vocs {
struct bt_vcp_db *vdb;
struct vol_offset_state *vostate;
uint32_t vocs_audio_loc;
char *vocs_ao_dec;
struct gatt_db_attribute *service;
struct gatt_db_attribute *vos;
struct gatt_db_attribute *vos_ccc;
struct gatt_db_attribute *voal;
struct gatt_db_attribute *voal_ccc;
struct gatt_db_attribute *vo_cp;
struct gatt_db_attribute *voaodec;
struct gatt_db_attribute *voaodec_ccc;
};
struct aud_ip_st {
int8_t gain_setting;
uint8_t mute;
uint8_t gain_mode;
uint8_t chg_counter;
} __packed;
struct gain_setting_prop {
uint8_t gain_setting_units;
int8_t gain_setting_min;
int8_t gain_setting_max;
} __packed;
struct bt_aics_set_gain_setting {
uint8_t change_counter;
int8_t gain_setting;
} __packed;
struct bt_aics {
struct bt_vcp_db *vdb;
struct aud_ip_st *aud_ipst;
struct gain_setting_prop *gain_settingprop;
uint8_t aud_input_type;
uint8_t aud_input_status;
char *aud_input_descr;
struct gatt_db_attribute *service;
struct gatt_db_attribute *aud_ip_state;
struct gatt_db_attribute *aud_ip_state_ccc;
struct gatt_db_attribute *gain_stting_prop;
struct gatt_db_attribute *aud_ip_type;
struct gatt_db_attribute *aud_ip_status;
struct gatt_db_attribute *aud_ip_status_ccc;
struct gatt_db_attribute *aud_ip_cp;
struct gatt_db_attribute *aud_ip_dscrptn;
struct gatt_db_attribute *aud_ip_dscrptn_ccc;
};
static struct queue *vcp_db;
static struct queue *vcp_cbs;
static struct queue *sessions;
static char *iov_pull_string(struct iovec *iov)
{
char *res;
if (!iov)
return NULL;
res = malloc(iov->iov_len + 1);
if (!res)
return NULL;
if (iov->iov_len)
memcpy(res, iov->iov_base, iov->iov_len);
res[iov->iov_len] = 0;
util_iov_pull(iov, iov->iov_len);
return res;
}
static struct bt_vcp_db *vcp_get_vdb(struct bt_vcp *vcp)
{
if (!vcp)
return NULL;
if (vcp->ldb)
return vcp->ldb;
return NULL;
}
static struct vol_state *vdb_get_vstate(struct bt_vcp_db *vdb)
{
if (!vdb->vcs)
return NULL;
if (vdb->vcs->vstate)
return vdb->vcs->vstate;
return NULL;
}
static struct vol_offset_state *vdb_get_vostate(struct bt_vcp_db *vdb)
{
if (!vdb->vocs)
return NULL;
if (vdb->vocs->vostate)
return vdb->vocs->vostate;
return NULL;
}
static struct bt_vcs *vcp_get_vcs(struct bt_vcp *vcp)
{
if (!vcp)
return NULL;
if (vcp->rdb->vcs)
return vcp->rdb->vcs;
vcp->rdb->vcs = new0(struct bt_vcs, 1);
vcp->rdb->vcs->vdb = vcp->rdb;
return vcp->rdb->vcs;
}
static struct bt_vocs *vcp_get_vocs(struct bt_vcp *vcp)
{
if (!vcp)
return NULL;
if (vcp->rdb->vocs)
return vcp->rdb->vocs;
vcp->rdb->vocs = new0(struct bt_vocs, 1);
vcp->rdb->vocs->vdb = vcp->rdb;
return vcp->rdb->vocs;
}
static struct bt_aics *vcp_get_aics(struct bt_vcp *vcp)
{
if (!vcp)
return NULL;
if (vcp->rdb->aics)
return vcp->rdb->aics;
vcp->rdb->aics = new0(struct bt_aics, 1);
vcp->rdb->aics->vdb = vcp->rdb;
return vcp->rdb->aics;
}
static void vcp_remote_client_attached(void *data, void *user_data)
{
struct bt_vcp_cb *cb = data;
struct bt_vcp *vcp = user_data;
cb->attached(vcp, cb->user_data);
}
static void vcp_remote_client_detached(void *data, void *user_data)
{
struct bt_vcp_cb *cb = data;
struct bt_vcp *vcp = user_data;
cb->detached(vcp, cb->user_data);
}
static void vcp_client_op_clear(struct bt_vcp_client_op *op)
{
if (op->timeout_id)
timeout_remove(op->timeout_id);
memset(op, 0, sizeof(*op));
}
void bt_vcp_detach(struct bt_vcp *vcp)
{
if (!queue_remove(sessions, vcp))
return;
if (vcp->idle_id) {
bt_gatt_client_idle_unregister(vcp->client, vcp->idle_id);
vcp->idle_id = 0;
}
if (vcp->client) {
bt_gatt_client_unref(vcp->client);
vcp->client = NULL;
}
vcp_client_op_clear(&vcp->pending_op);
}
static void vcp_db_free(void *data)
{
struct bt_vcp_db *vdb = data;
if (!vdb)
return;
gatt_db_unref(vdb->db);
free(vdb->vcs);
free(vdb->vocs);
free(vdb->aics);
free(vdb);
}
static void vcp_free(void *data)
{
struct bt_vcp *vcp = data;
bt_vcp_detach(vcp);
vcp_db_free(vcp->rdb);
queue_destroy(vcp->pending, NULL);
free(vcp);
}
bool bt_vcp_set_user_data(struct bt_vcp *vcp, void *user_data)
{
if (!vcp)
return false;
vcp->user_data = user_data;
return true;
}
static bool vcp_db_match(const void *data, const void *match_data)
{
const struct bt_vcp_db *vdb = data;
const struct gatt_db *db = match_data;
return (vdb->db == db);
}
struct bt_att *bt_vcp_get_att(struct bt_vcp *vcp)
{
if (!vcp)
return NULL;
if (vcp->att)
return vcp->att;
return bt_gatt_client_get_att(vcp->client);
}
struct bt_vcp *bt_vcp_ref(struct bt_vcp *vcp)
{
if (!vcp)
return NULL;
__sync_fetch_and_add(&vcp->ref_count, 1);
return vcp;
}
void bt_vcp_unref(struct bt_vcp *vcp)
{
if (!vcp)
return;
if (__sync_sub_and_fetch(&vcp->ref_count, 1))
return;
vcp_free(vcp);
}
static void vcp_debug(struct bt_vcp *vcp, const char *format, ...)
{
va_list ap;
if (!vcp || !format || !vcp->debug_func)
return;
va_start(ap, format);
util_debug_va(vcp->debug_func, vcp->debug_data, format, ap);
va_end(ap);
}
static void vcp_disconnected(int err, void *user_data)
{
/* called only when this device is acting a a server */
struct bt_vcp *vcp = user_data;
DBG(vcp, "vcp %p disconnected err %d", vcp, err);
bt_vcp_detach(vcp);
queue_foreach(vcp_cbs, vcp_remote_client_detached, vcp);
}
static struct bt_vcp *vcp_get_session(struct bt_att *att, struct gatt_db *db)
{
const struct queue_entry *entry;
struct bt_vcp *vcp;
for (entry = queue_get_entries(sessions); entry; entry = entry->next) {
struct bt_vcp *vcp = entry->data;
if (att == bt_vcp_get_att(vcp))
return vcp;
}
/* called only when this device is acting a a server */
vcp = bt_vcp_new(db, NULL);
vcp->att = att;
queue_foreach(vcp_cbs, vcp_remote_client_attached, vcp);
bt_att_register_disconnect(att, vcp_disconnected, vcp, NULL);
if (!sessions)
sessions = queue_new();
queue_push_tail(sessions, vcp);
return vcp;
}
static uint8_t vcs_rel_vol_down(struct bt_vcs *vcs, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct vol_state *vstate;
uint8_t *change_counter;
DBG(vcp, "Volume Down");
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
return 0;
}
vstate = vdb_get_vstate(vdb);
if (!vstate) {
DBG(vcp, "error: VSTATE not available");
return 0;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter)
return 0;
if (*change_counter != vstate->counter) {
DBG(vcp, "Change Counter Mismatch Volume not decremented!");
return BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
}
vstate->vol_set = MAX((vstate->vol_set - VCP_STEP_SIZE), 0);
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
vcp->volume = vstate->vol_set;
if (vcp->volume_changed)
vcp->volume_changed(vcp, vcp->volume);
gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate,
sizeof(struct vol_state),
bt_vcp_get_att(vcp));
return 0;
}
static uint8_t vcs_rel_vol_up(struct bt_vcs *vcs, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct vol_state *vstate;
uint8_t *change_counter;
DBG(vcp, "Volume Up");
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
return 0;
}
vstate = vdb_get_vstate(vdb);
if (!vstate) {
DBG(vcp, "error: VCP database not available");
return 0;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter)
return 0;
if (*change_counter != vstate->counter) {
DBG(vcp, "Change Counter Mismatch Volume not decremented!");
return BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
}
vstate->vol_set = MIN((vstate->vol_set + VCP_STEP_SIZE), 255);
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
vcp->volume = vstate->vol_set;
if (vcp->volume_changed)
vcp->volume_changed(vcp, vcp->volume);
gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate,
sizeof(struct vol_state),
bt_vcp_get_att(vcp));
return 0;
}
static uint8_t vcs_unmute_rel_vol_down(struct bt_vcs *vcs, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct vol_state *vstate;
uint8_t *change_counter;
DBG(vcp, "Un Mute and Volume Down");
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
return 0;
}
vstate = vdb_get_vstate(vdb);
if (!vstate) {
DBG(vcp, "error: VCP database not available");
return 0;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter)
return 0;
if (*change_counter != vstate->counter) {
DBG(vcp, "Change Counter Mismatch Volume not decremented!");
return BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
}
vstate->mute = 0x00;
vstate->vol_set = MAX((vstate->vol_set - VCP_STEP_SIZE), 0);
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
vcp->volume = vstate->vol_set;
if (vcp->volume_changed)
vcp->volume_changed(vcp, vcp->volume);
gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate,
sizeof(struct vol_state),
bt_vcp_get_att(vcp));
return 0;
}
static uint8_t vcs_unmute_rel_vol_up(struct bt_vcs *vcs, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct vol_state *vstate;
uint8_t *change_counter;
DBG(vcp, "UN Mute and Volume Up");
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
return 0;
}
vstate = vdb_get_vstate(vdb);
if (!vstate) {
DBG(vcp, "error: VSTATE not available");
return 0;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter)
return 0;
if (*change_counter != vstate->counter) {
DBG(vcp, "Change Counter Mismatch Volume not decremented!");
return BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
}
vstate->mute = 0x00;
vstate->vol_set = MIN((vstate->vol_set + VCP_STEP_SIZE), 255);
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
vcp->volume = vstate->vol_set;
if (vcp->volume_changed)
vcp->volume_changed(vcp, vcp->volume);
gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate,
sizeof(struct vol_state),
bt_vcp_get_att(vcp));
return 0;
}
static uint8_t vcs_set_absolute_vol(struct bt_vcs *vcs, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct vol_state *vstate;
struct bt_vcs_ab_vol *req;
DBG(vcp, "Set Absolute Volume");
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
return 0;
}
vstate = vdb_get_vstate(vdb);
if (!vstate) {
DBG(vcp, "error: VSTATE not available");
return 0;
}
req = util_iov_pull_mem(iov, sizeof(*req));
if (!req)
return 0;
if (req->change_counter != vstate->counter) {
DBG(vcp, "Change Counter Mismatch Volume not decremented!");
return BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
}
vstate->vol_set = req->vol_set;
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
vcp->volume = vstate->vol_set;
if (vcp->volume_changed)
vcp->volume_changed(vcp, vcp->volume);
gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate,
sizeof(struct vol_state),
bt_vcp_get_att(vcp));
return 0;
}
static uint8_t vcs_unmute(struct bt_vcs *vcs, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct vol_state *vstate;
uint8_t *change_counter;
DBG(vcp, "Un Mute");
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
return 0;
}
vstate = vdb_get_vstate(vdb);
if (!vstate) {
DBG(vcp, "error: VSTATE not available");
return 0;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter)
return 0;
if (*change_counter != vstate->counter) {
DBG(vcp, "Change Counter Mismatch Volume not decremented!");
return BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
}
vstate->mute = 0x00;
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate,
sizeof(struct vol_state),
bt_vcp_get_att(vcp));
return 0;
}
static uint8_t vcs_mute(struct bt_vcs *vcs, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct vol_state *vstate;
uint8_t *change_counter;
DBG(vcp, "MUTE");
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
return 0;
}
vstate = vdb_get_vstate(vdb);
if (!vstate) {
DBG(vcp, "error: VSTATE not available");
return 0;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter)
return 0;
if (*change_counter != vstate->counter) {
DBG(vcp, "Change Counter Mismatch Volume not decremented!");
return BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
}
vstate->mute = 0x01;
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
return 0;
}
static uint8_t vocs_set_vol_offset(struct bt_vocs *vocs, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct vol_offset_state *vstate, state;
struct bt_vocs_set_vol_off *req;
DBG(vcp, "Set Volume Offset");
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
return 0;
}
vstate = vdb_get_vostate(vdb);
if (!vstate) {
DBG(vcp, "error: VSTATE not available");
return 0;
}
req = util_iov_pull_mem(iov, sizeof(*req));
if (!req)
return 0;
if (req->change_counter != vstate->counter) {
DBG(vcp, "Change Counter Mismatch Volume not decremented!");
return BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
}
vstate->vol_offset = le16_to_cpu(req->set_vol_offset);
if (vstate->vol_offset > VOCS_VOL_OFFSET_UPPER_LIMIT ||
vstate->vol_offset < VOCS_VOL_OFFSET_LOWER_LIMIT) {
DBG(vcp, "error: Value Out of Range");
return BT_ATT_ERROR_VALUE_OUT_OF_RANGE;
}
/* Increment Change Counter */
vstate->counter = -~vstate->counter;
/* Notify change */
state.vol_offset = req->set_vol_offset;
state.counter = vstate->counter;
gatt_db_attribute_notify(vdb->vocs->vos, (void *)&state, sizeof(state),
bt_vcp_get_att(vcp));
return 0;
}
#define BT_VCS_REL_VOL_DOWN 0x00
#define BT_VCS_REL_VOL_UP 0x01
#define BT_VCS_UNMUTE_REL_VOL_DOWN 0x02
#define BT_VCS_UNMUTE_REL_VOL_UP 0x03
#define BT_VCS_SET_ABSOLUTE_VOL 0x04
#define BT_VCS_UNMUTE 0x05
#define BT_VCS_MUTE 0x06
#define BT_VOCS_SET_VOL_OFFSET 0x01
#define VCS_OP(_str, _op, _size, _func) \
{ \
.str = _str, \
.op = _op, \
.size = _size, \
.func = _func, \
}
struct vcs_op_handler {
const char *str;
uint8_t op;
size_t size;
uint8_t (*func)(struct bt_vcs *vcs, struct bt_vcp *vcp,
struct iovec *iov);
} vcp_handlers[] = {
VCS_OP("Relative Volume Down", BT_VCS_REL_VOL_DOWN,
sizeof(uint8_t), vcs_rel_vol_down),
VCS_OP("Relative Volume Up", BT_VCS_REL_VOL_UP,
sizeof(uint8_t), vcs_rel_vol_up),
VCS_OP("Unmute - Relative Volume Down", BT_VCS_UNMUTE_REL_VOL_DOWN,
sizeof(uint8_t), vcs_unmute_rel_vol_down),
VCS_OP("Unmute - Relative Volume Up", BT_VCS_UNMUTE_REL_VOL_UP,
sizeof(uint8_t), vcs_unmute_rel_vol_up),
VCS_OP("Set Absolute Volume", BT_VCS_SET_ABSOLUTE_VOL,
sizeof(struct bt_vcs_ab_vol), vcs_set_absolute_vol),
VCS_OP("UnMute", BT_VCS_UNMUTE,
sizeof(uint8_t), vcs_unmute),
VCS_OP("Mute", BT_VCS_MUTE,
sizeof(uint8_t), vcs_mute),
{}
};
#define VOCS_OP(_str, _op, _size, _func) \
{ \
.str = _str, \
.op = _op, \
.size = _size, \
.func = _func, \
}
struct vocs_op_handler {
const char *str;
uint8_t op;
size_t size;
uint8_t (*func)(struct bt_vocs *vocs, struct bt_vcp *vcp,
struct iovec *iov);
} vocp_handlers[] = {
VOCS_OP("Set Volume Offset", BT_VOCS_SET_VOL_OFFSET,
sizeof(uint8_t), vocs_set_vol_offset),
{}
};
static void vcs_cp_write(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_vcs *vcs = user_data;
struct bt_vcp *vcp = vcp_get_session(att, vcs->vdb->db);
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = len,
};
uint8_t *vcp_op;
struct vcs_op_handler *handler;
uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
DBG(vcp, "VCP Control Point Write");
if (offset) {
DBG(vcp, "invalid offset %d", offset);
ret = BT_ATT_ERROR_INVALID_OFFSET;
goto respond;
}
if (len < sizeof(*vcp_op)) {
DBG(vcp, "invalid len %ld < %ld sizeof(*param)", len,
sizeof(*vcp_op));
ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
goto respond;
}
vcp_op = util_iov_pull_mem(&iov, sizeof(*vcp_op));
if (!vcp_op) {
DBG(vcp, "util_iov_pull_mem() returned NULL");
goto respond;
}
for (handler = vcp_handlers; handler && handler->str; handler++) {
if (handler->op != *vcp_op)
continue;
if (iov.iov_len < handler->size) {
DBG(vcp, "invalid len %ld < %ld handler->size", len,
handler->size);
ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
goto respond;
}
break;
}
if (handler && handler->str) {
DBG(vcp, "%s", handler->str);
ret = handler->func(vcs, vcp, &iov);
} else {
DBG(vcp, "Unknown opcode 0x%02x", *vcp_op);
ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
}
respond:
gatt_db_attribute_write_result(attrib, id, ret);
}
static void vocs_cp_write(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_vocs *vocs = user_data;
struct bt_vcp *vcp = vcp_get_session(att, vocs->vdb->db);
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = len,
};
uint8_t *vcp_op;
struct vocs_op_handler *handler;
uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
DBG(vcp, "VOCP Control Point Write");
if (offset) {
DBG(vcp, "invalid offset %d", offset);
ret = BT_ATT_ERROR_INVALID_OFFSET;
goto respond;
}
if (len < sizeof(*vcp_op)) {
DBG(vcp, "invalid len %ld < %ld sizeof(*param)", len,
sizeof(*vcp_op));
ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
goto respond;
}
vcp_op = util_iov_pull_mem(&iov, sizeof(*vcp_op));
if (!vcp_op) {
DBG(vcp, "util_iov_pull_mem() returned NULL");
goto respond;
}
for (handler = vocp_handlers; handler && handler->str; handler++) {
if (handler->op != *vcp_op)
continue;
if (iov.iov_len < handler->size) {
DBG(vcp, "invalid len %ld < %ld handler->size", len,
handler->size);
ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
goto respond;
}
break;
}
if (handler && handler->str) {
DBG(vcp, "%s", handler->str);
ret = handler->func(vocs, vcp, &iov);
} else {
DBG(vcp, "Unknown opcode 0x%02x", *vcp_op);
ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
}
respond:
gatt_db_attribute_write_result(attrib, id, ret);
}
static void vcs_state_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_vcs *vcs = user_data;
struct iovec iov;
iov.iov_base = vcs->vstate;
iov.iov_len = sizeof(*vcs->vstate);
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
iov.iov_len);
}
static void vocs_state_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_vocs *vocs = user_data;
struct vol_offset_state state;
state.vol_offset = cpu_to_le16(vocs->vostate->vol_offset);
state.counter = vocs->vostate->counter;
gatt_db_attribute_read_result(attrib, id, 0, (void *)&state,
sizeof(state));
}
static void vcs_flag_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_vcs *vcs = user_data;
struct iovec iov;
iov.iov_base = &vcs->vol_flag;
iov.iov_len = sizeof(vcs->vol_flag);
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
iov.iov_len);
}
static void vocs_voal_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_vocs *vocs = user_data;
uint32_t loc;
loc = cpu_to_le32(vocs->vocs_audio_loc);
gatt_db_attribute_read_result(attrib, id, 0, (void *)&loc,
sizeof(loc));
}
static void vocs_voaodec_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_vocs *vocs = user_data;
struct iovec iov;
iov.iov_base = vocs->vocs_ao_dec;
iov.iov_len = strlen(vocs->vocs_ao_dec);
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
iov.iov_len);
}
static void aics_input_state_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_aics *aics = user_data;
struct iovec iov;
iov.iov_base = aics->aud_ipst;
iov.iov_len = sizeof(*aics->aud_ipst);
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
iov.iov_len);
}
static void aics_gain_setting_prop_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_aics *aics = user_data;
struct iovec iov;
iov.iov_base = aics->gain_settingprop;
iov.iov_len = sizeof(*aics->gain_settingprop);
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
iov.iov_len);
}
static void aics_audio_input_type_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_aics *aics = user_data;
struct iovec iov;
iov.iov_base = &aics->aud_input_type;
iov.iov_len = sizeof(aics->aud_input_type);
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
iov.iov_len);
}
static void aics_input_status_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_aics *aics = user_data;
struct iovec iov;
iov.iov_base = &aics->aud_input_status;
iov.iov_len = sizeof(aics->aud_input_status);
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
iov.iov_len);
}
static struct aud_ip_st *vdb_get_audipst(struct bt_vcp_db *vdb)
{
if (!vdb->aics)
return NULL;
if (vdb->aics->aud_ipst)
return vdb->aics->aud_ipst;
return NULL;
}
static struct gain_setting_prop *vdb_get_gainsettingprop(
struct bt_vcp_db *vdb)
{
if (!vdb->aics)
return NULL;
if (vdb->aics->gain_settingprop)
return vdb->aics->gain_settingprop;
return NULL;
}
static uint8_t aics_set_gain_setting(struct bt_aics *aics,
struct bt_vcp *vcp, struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct aud_ip_st *audipst;
struct bt_aics_set_gain_setting *req;
struct gain_setting_prop *gainsettngprop;
uint8_t ret = 1;
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
ret = 0;
goto respond;
}
audipst = vdb_get_audipst(vdb);
if (!audipst) {
DBG(vcp, "error: Audio Input State value is not available");
ret = 0;
goto respond;
}
req = util_iov_pull_mem(iov, sizeof(*req));
if (!req) {
ret = 0;
goto respond;
}
if (req->change_counter != audipst->chg_counter) {
DBG(vcp, "Change Counter Mismatch Audio Input State!");
ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
goto respond;
}
if (audipst->gain_mode != AICS_GAIN_MODE_MANUAL_ONLY &&
audipst->gain_mode != AICS_GAIN_MODE_MANUAL) {
DBG(vcp, "Gain Mode is not Manual only or Manual");
ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
goto respond;
}
gainsettngprop = vdb_get_gainsettingprop(vdb);
if (req->gain_setting > gainsettngprop->gain_setting_max ||
req->gain_setting < gainsettngprop->gain_setting_min) {
DBG(vcp, "error: Value Out of Range");
ret = BT_ATT_AICS_ERROR_VALUE_OUT_OF_RANGE;
goto respond;
}
audipst->gain_setting = req->gain_setting;
/*Increment Change Counter*/
audipst->chg_counter = -~audipst->chg_counter;
gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst,
sizeof(struct aud_ip_st),
bt_vcp_get_att(vcp));
ret = 0;
respond:
return ret;
}
static uint8_t aics_unmute(struct bt_aics *aics, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct aud_ip_st *audipst;
uint8_t *change_counter;
uint8_t ret = 1;
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
ret = 0;
goto respond;
}
audipst = vdb_get_audipst(vdb);
if (!audipst) {
DBG(vcp, "error: Audio Input State value is not available");
ret = 0;
goto respond;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter) {
ret = 0;
goto respond;
}
if (*change_counter != audipst->chg_counter) {
DBG(vcp, "Change Counter Mismatch Audio Input State!");
ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
goto respond;
}
if (audipst->mute == AICS_DISABLED) {
DBG(vcp, "Mute state is Disabled!");
ret = BT_ATT_AICS_ERROR_MUTE_DISABLED;
goto respond;
}
audipst->mute = AICS_NOT_MUTED;
/*Increment Change Counter*/
audipst->chg_counter = -~audipst->chg_counter;
gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst,
sizeof(struct aud_ip_st),
bt_vcp_get_att(vcp));
ret = 0;
respond:
return ret;
}
static uint8_t aics_mute(struct bt_aics *aics, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct aud_ip_st *audipst;
uint8_t *change_counter;
uint8_t ret = 1;
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
ret = 0;
goto respond;
}
audipst = vdb_get_audipst(vdb);
if (!audipst) {
DBG(vcp, "error: Audio Input State value is not available");
ret = 0;
goto respond;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter) {
ret = 0;
goto respond;
}
if (*change_counter != audipst->chg_counter) {
DBG(vcp, "Change Counter Mismatch Audio Input State!");
ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
goto respond;
}
if (audipst->mute == AICS_DISABLED) {
DBG(vcp, "Mute state is Disabled!");
ret = BT_ATT_AICS_ERROR_MUTE_DISABLED;
goto respond;
}
audipst->mute = AICS_MUTED;
/*Increment Change Counter*/
audipst->chg_counter = -~audipst->chg_counter;
gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst,
sizeof(struct aud_ip_st),
bt_vcp_get_att(vcp));
ret = 0;
respond:
return ret;
}
static uint8_t aics_set_manual_gain_mode(struct bt_aics *aics,
struct bt_vcp *vcp, struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct aud_ip_st *audipst;
uint8_t *change_counter;
uint8_t ret = 1;
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
ret = 0;
goto respond;
}
audipst = vdb_get_audipst(vdb);
if (!audipst) {
DBG(vcp, "error: Audio Input State value is not available");
ret = 0;
goto respond;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter) {
ret = 0;
goto respond;
}
if (*change_counter != audipst->chg_counter) {
DBG(vcp, "Change Counter Mismatch Audio Input State!");
ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
goto respond;
}
if (audipst->gain_mode == AICS_GAIN_MODE_AUTO_ONLY ||
audipst->gain_mode == AICS_GAIN_MODE_MANUAL_ONLY) {
DBG(vcp, "error!! gain mode is Automatic only or Manual only");
ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
goto respond;
}
if (audipst->gain_mode == AICS_GAIN_MODE_AUTO) {
audipst->gain_mode = AICS_GAIN_MODE_MANUAL;
/*Increment Change Counter*/
audipst->chg_counter = -~audipst->chg_counter;
gatt_db_attribute_notify(vdb->aics->aud_ip_state,
(void *)audipst,
sizeof(struct aud_ip_st),
bt_vcp_get_att(vcp));
ret = 0;
} else {
DBG(vcp,
"error!! Gain mode field value not Automatic");
ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
}
respond:
return ret;
}
static uint8_t aics_set_auto_gain_mode(struct bt_aics *aics, struct bt_vcp *vcp,
struct iovec *iov)
{
struct bt_vcp_db *vdb;
struct aud_ip_st *audipst;
uint8_t *change_counter;
uint8_t ret = 1;
vdb = vcp_get_vdb(vcp);
if (!vdb) {
DBG(vcp, "error: VDB not available");
ret = 0;
goto respond;
}
audipst = vdb_get_audipst(vdb);
if (!audipst) {
DBG(vcp, "error: Audio Input State value is not available");
ret = 0;
goto respond;
}
change_counter = util_iov_pull_mem(iov, sizeof(*change_counter));
if (!change_counter) {
ret = 0;
goto respond;
}
if (*change_counter != audipst->chg_counter) {
DBG(vcp, "Change Counter Mismatch Audio Input State!");
ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
goto respond;
}
if (audipst->gain_mode == AICS_GAIN_MODE_AUTO_ONLY ||
audipst->gain_mode == AICS_GAIN_MODE_MANUAL_ONLY) {
DBG(vcp, "error!! gain mode is Automatic only or Manual only");
ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
goto respond;
}
if (audipst->gain_mode == AICS_GAIN_MODE_MANUAL) {
audipst->gain_mode = AICS_GAIN_MODE_AUTO;
/*Increment Change Counter*/
audipst->chg_counter = -~audipst->chg_counter;
gatt_db_attribute_notify(vdb->aics->aud_ip_state,
(void *)audipst,
sizeof(struct aud_ip_st), bt_vcp_get_att(vcp));
ret = 0;
} else {
DBG(vcp, "error!! Gain mode field value is not Manual");
ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
}
respond:
return ret;
}
#define AICS_OP(_str, _op, _size, _func) \
{ \
.str = _str, \
.op = _op, \
.size = _size, \
.func = _func, \
}
struct aics_op_handler {
const char *str;
uint8_t op;
size_t size;
uint8_t (*func)(struct bt_aics *aics, struct bt_vcp *vcp,
struct iovec *iov);
} aics_handlers[] = {
AICS_OP("Set Gain Setting", BT_AICS_SET_GAIN_SETTING,
sizeof(struct bt_aics_set_gain_setting),
aics_set_gain_setting),
AICS_OP("Unmute", BT_AICS_UNMUTE,
sizeof(uint8_t), aics_unmute),
AICS_OP("Mute", BT_AICS_MUTE,
sizeof(uint8_t), aics_mute),
AICS_OP("Set Manual Gain Mode", BT_AICS_SET_MANUAL_GAIN_MODE,
sizeof(uint8_t), aics_set_manual_gain_mode),
AICS_OP("Set Automatic Gain Mode", BT_AICS_SET_AUTO_GAIN_MODE,
sizeof(uint8_t), aics_set_auto_gain_mode),
{}
};
static void aics_ip_cp_write(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_aics *aics = user_data;
struct bt_vcp *vcp = vcp_get_session(att, aics->vdb->db);
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = len,
};
uint8_t *aics_op;
struct aics_op_handler *handler;
uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
DBG(vcp, "AICS Control Point Write");
if (offset) {
DBG(vcp, "invalid offset %d", offset);
ret = BT_ATT_ERROR_INVALID_OFFSET;
goto respond;
}
if (len < sizeof(*aics_op)) {
DBG(vcp, "invalid len %ld < %ld sizeof(*param)", len,
sizeof(*aics_op));
ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
goto respond;
}
aics_op = util_iov_pull_mem(&iov, sizeof(*aics_op));
if (!aics_op) {
DBG(vcp, "util_iov_pull_mem() returned NULL");
goto respond;
}
for (handler = aics_handlers; handler && handler->str; handler++) {
if (handler->op != *aics_op)
continue;
if (iov.iov_len < handler->size) {
DBG(vcp, "invalid len %ld < %ld handler->size", len,
handler->size);
ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
goto respond;
}
break;
}
if (handler && handler->str) {
DBG(vcp, "%s", handler->str);
ret = handler->func(aics, vcp, &iov);
} else {
DBG(vcp, "Unknown opcode 0x%02x", *aics_op);
ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
}
respond:
gatt_db_attribute_write_result(attrib, id, ret);
}
static void aics_input_descr_read(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
struct bt_aics *aics = user_data;
struct iovec iov;
iov.iov_base = aics->aud_input_descr;
iov.iov_len = strlen(aics->aud_input_descr);
gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
iov.iov_len);
}
static void aics_input_descr_write(struct gatt_db_attribute *attrib,
unsigned int id, uint16_t offset,
const uint8_t *value, size_t len,
uint8_t opcode, struct bt_att *att,
void *user_data)
{
/* TODO : AICS optional feature */
}
static struct bt_vcs *vcs_new(struct gatt_db *db, struct bt_vcp_db *vdb)
{
struct bt_vcs *vcs;
struct vol_state *vstate;
bt_uuid_t uuid;
if (!db)
return NULL;
vcs = new0(struct bt_vcs, 1);
vstate = new0(struct vol_state, 1);
vcs->vstate = vstate;
vcs->vol_flag = USERSET_VOLUME_SETTING;
/* Populate DB with VCS attributes */
bt_uuid16_create(&uuid, VCS_UUID);
vcs->service = gatt_db_add_service(db, &uuid, true,
VCS_TOTAL_NUM_HANDLES);
gatt_db_service_add_included(vcs->service, vdb->vocs->service);
gatt_db_service_set_active(vdb->vocs->service, true);
gatt_db_service_add_included(vcs->service, vdb->aics->service);
gatt_db_service_set_active(vdb->aics->service, true);
bt_uuid16_create(&uuid, VOL_STATE_CHRC_UUID);
vcs->vs = gatt_db_service_add_characteristic(vcs->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
vcs_state_read, NULL,
vcs);
vcs->vs_ccc = gatt_db_service_add_ccc(vcs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, VOL_CP_CHRC_UUID);
vcs->vol_cp = gatt_db_service_add_characteristic(vcs->service,
&uuid,
BT_ATT_PERM_WRITE,
BT_GATT_CHRC_PROP_WRITE,
NULL, vcs_cp_write,
vcs);
bt_uuid16_create(&uuid, VOL_FLAG_CHRC_UUID);
vcs->vf = gatt_db_service_add_characteristic(vcs->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
vcs_flag_read, NULL,
vcs);
vcs->vf_ccc = gatt_db_service_add_ccc(vcs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
gatt_db_service_set_active(vcs->service, true);
return vcs;
}
static struct bt_vocs *vocs_new(struct gatt_db *db)
{
struct bt_vocs *vocs;
struct vol_offset_state *vostate;
bt_uuid_t uuid;
if (!db)
return NULL;
vocs = new0(struct bt_vocs, 1);
vostate = new0(struct vol_offset_state, 1);
vocs->vostate = vostate;
vocs->vocs_audio_loc = BT_VCP_FRONT_LEFT;
vocs->vocs_ao_dec = "Left Speaker";
/* Populate DB with VOCS attributes */
bt_uuid16_create(&uuid, VOL_OFFSET_CS_UUID);
vocs->service = gatt_db_add_service(db, &uuid, false, 12);
bt_uuid16_create(&uuid, VOCS_STATE_CHAR_UUID);
vocs->vos = gatt_db_service_add_characteristic(vocs->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
vocs_state_read, NULL,
vocs);
vocs->vos_ccc = gatt_db_service_add_ccc(vocs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, VOCS_AUDIO_LOC_CHRC_UUID);
vocs->voal = gatt_db_service_add_characteristic(vocs->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
vocs_voal_read, NULL,
vocs);
vocs->voal_ccc = gatt_db_service_add_ccc(vocs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, VOCS_CP_CHRC_UUID);
vocs->vo_cp = gatt_db_service_add_characteristic(vocs->service,
&uuid,
BT_ATT_PERM_WRITE,
BT_GATT_CHRC_PROP_WRITE,
NULL, vocs_cp_write,
vocs);
bt_uuid16_create(&uuid, VOCS_AUDIO_OP_DESC_CHAR_UUID);
vocs->voaodec = gatt_db_service_add_characteristic(vocs->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
vocs_voaodec_read, NULL,
vocs);
vocs->voaodec_ccc = gatt_db_service_add_ccc(vocs->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
return vocs;
}
static struct bt_aics *aics_new(struct gatt_db *db)
{
struct bt_aics *aics;
struct aud_ip_st *aics_aud_ip_st;
struct gain_setting_prop *aics_gain_settng_prop;
char *ip_descr;
char ip_descr_str[] = "Blueooth";
bt_uuid_t uuid;
if (!db)
return NULL;
aics = new0(struct bt_aics, 1);
aics_aud_ip_st = new0(struct aud_ip_st, 1);
aics_gain_settng_prop = new0(struct gain_setting_prop, 1);
ip_descr = malloc(256);
memset(ip_descr, 0, 256);
aics_aud_ip_st->mute = AICS_NOT_MUTED;
aics_aud_ip_st->gain_mode = AICS_GAIN_MODE_MANUAL;
aics_aud_ip_st->gain_setting = AICS_GAIN_SETTING_DEFAULT_VALUE;
aics->aud_ipst = aics_aud_ip_st;
aics_gain_settng_prop->gain_setting_units = AICS_GAIN_SETTING_UNITS;
aics_gain_settng_prop->gain_setting_max = AICS_GAIN_SETTING_MAX_VALUE;
aics_gain_settng_prop->gain_setting_min = AICS_GAIN_SETTING_MIN_VALUE;
aics->gain_settingprop = aics_gain_settng_prop;
aics->aud_input_type = AICS_AUD_IP_TYPE_BLUETOOTH;
aics->aud_input_status = AICS_AUD_IP_STATUS_ACTIVE;
memcpy(ip_descr, ip_descr_str, strlen(ip_descr_str));
aics->aud_input_descr = ip_descr;
/* Populate DB with AICS attributes */
bt_uuid16_create(&uuid, AUDIO_INPUT_CS_UUID);
aics->service = gatt_db_add_service(db, &uuid, false,
AICS_TOTAL_NUM_HANDLES);
bt_uuid16_create(&uuid, AICS_INPUT_STATE_CHAR_UUID);
aics->aud_ip_state = gatt_db_service_add_characteristic(aics->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
aics_input_state_read,
NULL,
aics);
aics->aud_ip_state_ccc = gatt_db_service_add_ccc(aics->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, AICS_GAIN_SETTING_PROP_CHAR_UUID);
aics->gain_stting_prop = gatt_db_service_add_characteristic(
aics->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ,
aics_gain_setting_prop_read, NULL,
aics);
bt_uuid16_create(&uuid, AICS_AUDIO_INPUT_TYPE_CHAR_UUID);
aics->aud_ip_type = gatt_db_service_add_characteristic(aics->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ,
aics_audio_input_type_read, NULL,
aics);
bt_uuid16_create(&uuid, AICS_INPUT_STATUS_CHAR_UUID);
aics->aud_ip_status = gatt_db_service_add_characteristic(aics->service,
&uuid,
BT_ATT_PERM_READ,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_NOTIFY,
aics_input_status_read, NULL,
aics);
aics->aud_ip_status_ccc = gatt_db_service_add_ccc(aics->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
bt_uuid16_create(&uuid, AICS_AUDIO_INPUT_CP_CHRC_UUID);
aics->aud_ip_cp = gatt_db_service_add_characteristic(aics->service,
&uuid,
BT_ATT_PERM_WRITE,
BT_GATT_CHRC_PROP_WRITE,
NULL, aics_ip_cp_write,
aics);
bt_uuid16_create(&uuid, AICS_INPUT_DESCR_CHAR_UUID);
aics->aud_ip_dscrptn = gatt_db_service_add_characteristic(aics->service,
&uuid,
BT_ATT_PERM_READ |
BT_ATT_PERM_WRITE,
BT_GATT_CHRC_PROP_READ |
BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP |
BT_GATT_CHRC_PROP_NOTIFY,
aics_input_descr_read,
aics_input_descr_write,
aics);
aics->aud_ip_dscrptn_ccc = gatt_db_service_add_ccc(aics->service,
BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
return aics;
}
static struct bt_vcp_db *vcp_db_new(struct gatt_db *db)
{
struct bt_vcp_db *vdb;
if (!db)
return NULL;
vdb = new0(struct bt_vcp_db, 1);
vdb->db = gatt_db_ref(db);
if (!vcp_db)
vcp_db = queue_new();
vdb->vocs = vocs_new(db);
vdb->vocs->vdb = vdb;
vdb->aics = aics_new(db);
vdb->aics->vdb = vdb;
vdb->vcs = vcs_new(db, vdb);
vdb->vcs->vdb = vdb;
queue_push_tail(vcp_db, vdb);
return vdb;
}
static struct bt_vcp_db *vcp_get_db(struct gatt_db *db)
{
struct bt_vcp_db *vdb;
vdb = queue_find(vcp_db, vcp_db_match, db);
if (vdb)
return vdb;
return vcp_db_new(db);
}
void bt_vcp_add_db(struct gatt_db *db)
{
vcp_db_new(db);
}
bool bt_vcp_set_debug(struct bt_vcp *vcp, bt_vcp_debug_func_t func,
void *user_data, bt_vcp_destroy_func_t destroy)
{
if (!vcp)
return false;
if (vcp->debug_destroy)
vcp->debug_destroy(vcp->debug_data);
vcp->debug_func = func;
vcp->debug_destroy = destroy;
vcp->debug_data = user_data;
return true;
}
bool bt_vcp_set_volume_callback(struct bt_vcp *vcp,
bt_vcp_volume_func_t volume_changed)
{
if (!vcp)
return false;
vcp->volume_changed = volume_changed;
return true;
}
unsigned int bt_vcp_register(bt_vcp_func_t attached, bt_vcp_func_t detached,
void *user_data)
{
struct bt_vcp_cb *cb;
static unsigned int id;
if (!attached && !detached)
return 0;
if (!vcp_cbs)
vcp_cbs = queue_new();
cb = new0(struct bt_vcp_cb, 1);
cb->id = ++id ? id : ++id;
cb->attached = attached;
cb->detached = detached;
cb->user_data = user_data;
queue_push_tail(vcp_cbs, cb);
return cb->id;
}
static bool match_id(const void *data, const void *match_data)
{
const struct bt_vcp_cb *cb = data;
unsigned int id = PTR_TO_UINT(match_data);
return (cb->id == id);
}
bool bt_vcp_unregister(unsigned int id)
{
struct bt_vcp_cb *cb;
cb = queue_remove_if(vcp_cbs, match_id, UINT_TO_PTR(id));
if (!cb)
return false;
free(cb);
return true;
}
struct bt_vcp *bt_vcp_new(struct gatt_db *ldb, struct gatt_db *rdb)
{
struct bt_vcp *vcp;
struct bt_vcp_db *vdb;
if (!ldb)
return NULL;
vdb = vcp_get_db(ldb);
if (!vdb)
return NULL;
vcp = new0(struct bt_vcp, 1);
vcp->ldb = vdb;
vcp->pending = queue_new();
if (!rdb)
goto done;
vdb = new0(struct bt_vcp_db, 1);
vdb->db = gatt_db_ref(rdb);
vcp->rdb = vdb;
done:
bt_vcp_ref(vcp);
return vcp;
}
static void vcp_set_volume_complete(struct bt_vcp *vcp)
{
bool resend = vcp->pending_op.resend;
uint8_t volume = vcp->pending_op.volume;
vcp_client_op_clear(&vcp->pending_op);
/* If there were more volume set ops while waiting for the one that
* completes, send request to set volume to the latest pending value.
*/
if (resend) {
DBG(vcp, "set pending volume 0x%x", volume);
bt_vcp_set_volume(vcp, volume);
}
}
static void vcp_vstate_notify(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
struct vol_state *vstate;
vstate = util_iov_pull_mem(&iov, sizeof(*vstate));
if (!vstate) {
DBG(vcp, "Invalid Vol State");
return;
}
DBG(vcp, "Vol Settings 0x%x", vstate->vol_set);
DBG(vcp, "Mute Status 0x%x", vstate->mute);
DBG(vcp, "Vol Counter 0x%x", vstate->counter);
vcp->volume = vstate->vol_set;
vcp->volume_counter = vstate->counter;
if (vcp->volume_changed)
vcp->volume_changed(vcp, vcp->volume);
vcp->pending_op.wait_notify = false;
if (!vcp->pending_op.wait_reply)
vcp_set_volume_complete(vcp);
}
static void vcp_volume_cp_sent(bool success, uint8_t err, void *user_data)
{
struct bt_vcp *vcp = user_data;
if (!success) {
if (err == BT_ATT_ERROR_INVALID_CHANGE_COUNTER)
DBG(vcp, "setting volume failed: invalid counter");
else
DBG(vcp, "setting volume failed: error 0x%x", err);
vcp_set_volume_complete(vcp);
} else {
vcp->pending_op.wait_reply = false;
if (!vcp->pending_op.wait_notify)
vcp_set_volume_complete(vcp);
}
}
static bool vcp_set_volume_timeout(void *data)
{
struct bt_vcp *vcp = data;
DBG(vcp, "setting volume: timeout");
vcp->pending_op.timeout_id = 0;
vcp_set_volume_complete(vcp);
return false;
}
static bool vcp_set_volume_client(struct bt_vcp *vcp, uint8_t volume)
{
struct bt_vcs_client_ab_vol req;
uint16_t value_handle;
struct bt_vcs *vcs = vcp_get_vcs(vcp);
if (!vcs) {
DBG(vcp, "error: vcs not available");
return false;
}
if (!vcs->vol_cp) {
DBG(vcp, "error: vol_cp characteristics not available");
return false;
}
if (!gatt_db_attribute_get_char_data(vcs->vol_cp, NULL, &value_handle,
NULL, NULL, NULL)) {
DBG(vcp, "error: vol_cp characteristics not available");
return false;
}
/* If there is another set volume op in flight, just update the wanted
* pending volume value. Req with the latest volume value is sent after
* the current one completes. This may skip over some volume changes,
* as it only sends a request for the final value.
*/
if (vcp->pending_op.timeout_id) {
vcp->pending_op.volume = volume;
vcp->pending_op.resend = true;
return true;
} else if (vcp->volume == volume) {
/* Do not set to current value, as that doesn't generate
* a notification
*/
return true;
}
req.op = BT_VCS_SET_ABSOLUTE_VOL;
req.vol_set = volume;
req.change_counter = vcp->volume_counter;
if (!bt_gatt_client_write_value(vcp->client, value_handle, (void *)&req,
sizeof(req), vcp_volume_cp_sent, vcp,
NULL)) {
DBG(vcp, "error writing volume");
return false;
}
vcp->pending_op.timeout_id = timeout_add(VCP_CLIENT_OP_TIMEOUT,
vcp_set_volume_timeout, vcp, NULL);
vcp->pending_op.wait_notify = true;
vcp->pending_op.wait_reply = true;
return true;
}
static bool vcp_set_volume_server(struct bt_vcp *vcp, uint8_t volume)
{
struct bt_vcp_db *vdb = vcp_get_vdb(vcp);
struct vol_state *vstate;
vcp->volume = volume;
if (!vdb) {
DBG(vcp, "error: VDB not available");
return false;
}
vstate = vdb_get_vstate(vdb);
if (!vstate) {
DBG(vcp, "error: VSTATE not available");
return false;
}
vstate->vol_set = vcp->volume;
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
gatt_db_attribute_notify(vdb->vcs->vs, (void *) vstate,
sizeof(struct vol_state), bt_vcp_get_att(vcp));
return true;
}
bool bt_vcp_set_volume(struct bt_vcp *vcp, uint8_t volume)
{
if (vcp->client)
return vcp_set_volume_client(vcp, volume);
else
return vcp_set_volume_server(vcp, volume);
}
uint8_t bt_vcp_get_volume(struct bt_vcp *vcp)
{
return vcp->volume;
}
static void vcp_voffset_state_notify(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
struct vol_offset_state *vostate;
vostate = util_iov_pull_mem(&iov, sizeof(*vostate));
if (!vostate) {
DBG(vcp, "Invalid Vol Offset State");
return;
}
DBG(vcp, "Vol Offset 0x%x", vostate->vol_offset);
DBG(vcp, "Vol Offset Counter 0x%x", vostate->counter);
}
static void vcp_audio_loc_notify(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
uint32_t audio_loc;
if (!util_iov_pull_le32(&iov, &audio_loc)) {
DBG(vcp, "Invalid VOCS Audio Location");
return;
}
DBG(vcp, "VOCS Audio Location 0x%x", audio_loc);
}
static void vcp_audio_descriptor_notify(struct bt_vcp *vcp,
uint16_t value_handle,
const uint8_t *value,
uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
char *vocs_audio_dec;
vocs_audio_dec = iov_pull_string(&iov);
if (!vocs_audio_dec)
return;
DBG(vcp, "VOCS Audio Descriptor 0x%s", vocs_audio_dec);
free(vocs_audio_dec);
}
static void vcp_vflag_notify(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
uint8_t vflag;
if (!util_iov_pull_u8(&iov, &vflag)) {
DBG(vcp, "Invalid Vol Flag");
return;
}
DBG(vcp, "Vol Flag 0x%x", vflag);
}
static void read_vol_flag(struct bt_vcp *vcp, bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *) value, .iov_len = length };
uint8_t vol_flag;
if (!success) {
DBG(vcp, "Unable to read Vol Flag: error 0x%02x", att_ecode);
return;
}
if (!util_iov_pull_u8(&iov, &vol_flag)) {
DBG(vcp, "Unable to get Vol Flag");
return;
}
DBG(vcp, "Vol Flag:%x", vol_flag);
}
static void read_vol_state(struct bt_vcp *vcp, bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct vol_state *vs;
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = length,
};
if (!success) {
DBG(vcp, "Unable to read Vol State: error 0x%02x", att_ecode);
return;
}
vs = util_iov_pull_mem(&iov, sizeof(*vs));
if (!vs) {
DBG(vcp, "Unable to get Vol State");
return;
}
DBG(vcp, "Vol Set:%x", vs->vol_set);
DBG(vcp, "Vol Mute:%x", vs->mute);
DBG(vcp, "Vol Counter:%x", vs->counter);
vcp->volume = vs->vol_set;
vcp->volume_counter = vs->counter;
}
static void read_vol_offset_state(struct bt_vcp *vcp, bool success,
uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct vol_offset_state *vos;
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = length,
};
if (!success) {
DBG(vcp, "Unable to read Vol Offset State: error 0x%02x",
att_ecode);
return;
}
vos = util_iov_pull_mem(&iov, sizeof(*vos));
if (!vos) {
DBG(vcp, "Unable to get Vol Offset State");
return;
}
DBG(vcp, "Vol Offset: 0x%04x", le16_to_cpu(vos->vol_offset));
DBG(vcp, "Vol Counter: 0x%02x", vos->counter);
}
static void read_vocs_audio_location(struct bt_vcp *vcp, bool success,
uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
uint32_t vocs_audio_loc;
if (!success) {
DBG(vcp, "Unable to read VOCS Audio Location: error 0x%02x",
att_ecode);
return;
}
if (!util_iov_pull_le32(&iov, &vocs_audio_loc)) {
DBG(vcp, "Invalid size for VOCS Audio Location");
return;
}
DBG(vcp, "VOCS Audio Loc: 0x%8x", vocs_audio_loc);
}
static void read_vocs_audio_descriptor(struct bt_vcp *vcp, bool success,
uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
char *vocs_ao_dec;
if (!success) {
DBG(vcp, "Unable to read VOCS Audio Descriptor: error 0x%02x",
att_ecode);
return;
}
vocs_ao_dec = iov_pull_string(&iov);
if (!vocs_ao_dec)
return;
DBG(vcp, "VOCS Audio Descriptor: %s", vocs_ao_dec);
free(vocs_ao_dec);
}
static void vcp_pending_destroy(void *data)
{
struct bt_vcp_pending *pending = data;
struct bt_vcp *vcp = pending->vcp;
if (queue_remove_if(vcp->pending, NULL, pending))
free(pending);
}
static void vcp_pending_complete(bool success, uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct bt_vcp_pending *pending = user_data;
if (pending->func)
pending->func(pending->vcp, success, att_ecode, value, length,
pending->user_data);
}
static void vcp_read_value(struct bt_vcp *vcp, uint16_t value_handle,
vcp_func_t func, void *user_data)
{
struct bt_vcp_pending *pending;
pending = new0(struct bt_vcp_pending, 1);
pending->vcp = vcp;
pending->func = func;
pending->user_data = user_data;
pending->id = bt_gatt_client_read_value(vcp->client, value_handle,
vcp_pending_complete, pending,
vcp_pending_destroy);
if (!pending->id) {
DBG(vcp, "Unable to send Read request");
free(pending);
return;
}
queue_push_tail(vcp->pending, pending);
}
static void vcp_register(uint16_t att_ecode, void *user_data)
{
struct bt_vcp_notify *notify = user_data;
if (att_ecode)
DBG(notify->vcp, "VCP register failed: 0x%04x", att_ecode);
}
static void vcp_notify(uint16_t value_handle, const uint8_t *value,
uint16_t length, void *user_data)
{
struct bt_vcp_notify *notify = user_data;
if (notify->func)
notify->func(notify->vcp, value_handle, value, length,
notify->user_data);
}
static void vcp_notify_destroy(void *data)
{
struct bt_vcp_notify *notify = data;
struct bt_vcp *vcp = notify->vcp;
if (queue_remove_if(vcp->notify, NULL, notify))
free(notify);
}
static unsigned int vcp_register_notify(struct bt_vcp *vcp,
uint16_t value_handle,
vcp_notify_t func,
void *user_data)
{
struct bt_vcp_notify *notify;
notify = new0(struct bt_vcp_notify, 1);
notify->vcp = vcp;
notify->func = func;
notify->user_data = user_data;
notify->id = bt_gatt_client_register_notify(vcp->client,
value_handle, vcp_register,
vcp_notify, notify,
vcp_notify_destroy);
if (!notify->id) {
DBG(vcp, "Unable to register for notifications");
free(notify);
return 0;
}
queue_push_tail(vcp->notify, notify);
return notify->id;
}
static void foreach_vcs_char(struct gatt_db_attribute *attr, void *user_data)
{
struct bt_vcp *vcp = user_data;
uint16_t value_handle;
bt_uuid_t uuid, uuid_vstate, uuid_cp, uuid_vflag;
struct bt_vcs *vcs;
if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
NULL, NULL, &uuid))
return;
bt_uuid16_create(&uuid_vstate, VOL_STATE_CHRC_UUID);
bt_uuid16_create(&uuid_cp, VOL_CP_CHRC_UUID);
bt_uuid16_create(&uuid_vflag, VOL_FLAG_CHRC_UUID);
if (!bt_uuid_cmp(&uuid, &uuid_vstate)) {
DBG(vcp, "VCS Vol state found: handle 0x%04x", value_handle);
vcs = vcp_get_vcs(vcp);
if (!vcs)
return;
vcs->vs = attr;
vcp_read_value(vcp, value_handle, read_vol_state, vcp);
vcp->vstate_id = vcp_register_notify(vcp, value_handle,
vcp_vstate_notify, NULL);
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_cp)) {
DBG(vcp, "VCS Volume CP found: handle 0x%04x", value_handle);
vcs = vcp_get_vcs(vcp);
if (!vcs)
return;
vcs->vol_cp = attr;
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_vflag)) {
DBG(vcp, "VCS Vol Flag found: handle 0x%04x", value_handle);
vcs = vcp_get_vcs(vcp);
if (!vcs)
return;
vcs->vf = attr;
vcp_read_value(vcp, value_handle, read_vol_flag, vcp);
vcp->vflag_id = vcp_register_notify(vcp, value_handle,
vcp_vflag_notify, NULL);
}
}
static void foreach_vocs_char(struct gatt_db_attribute *attr, void *user_data)
{
struct bt_vcp *vcp = user_data;
uint16_t value_handle;
bt_uuid_t uuid, uuid_vostate, uuid_audio_loc, uuid_vo_cp,
uuid_audio_op_decs;
struct bt_vocs *vocs;
if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
NULL, NULL, &uuid))
return;
bt_uuid16_create(&uuid_vostate, VOCS_STATE_CHAR_UUID);
bt_uuid16_create(&uuid_audio_loc, VOCS_AUDIO_LOC_CHRC_UUID);
bt_uuid16_create(&uuid_vo_cp, VOCS_CP_CHRC_UUID);
bt_uuid16_create(&uuid_audio_op_decs, VOCS_AUDIO_OP_DESC_CHAR_UUID);
if (!bt_uuid_cmp(&uuid, &uuid_vostate)) {
DBG(vcp, "VOCS Vol state found: handle 0x%04x", value_handle);
vocs = vcp_get_vocs(vcp);
if (!vocs || vocs->vos)
return;
vocs->vos = attr;
vcp_read_value(vcp, value_handle, read_vol_offset_state, vcp);
vcp->state_id = vcp_register_notify(vcp, value_handle,
vcp_voffset_state_notify, NULL);
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_audio_loc)) {
DBG(vcp, "VOCS Volume Audio Location found: handle 0x%04x",
value_handle);
vocs = vcp_get_vocs(vcp);
if (!vocs || vocs->voal)
return;
vocs->voal = attr;
vcp_read_value(vcp, value_handle, read_vocs_audio_location,
vcp);
vcp->audio_loc_id = vcp_register_notify(vcp, value_handle,
vcp_audio_loc_notify, NULL);
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_vo_cp)) {
DBG(vcp, "VOCS Volume CP found: handle 0x%04x", value_handle);
vocs = vcp_get_vocs(vcp);
if (!vocs || vocs->vo_cp)
return;
vocs->vo_cp = attr;
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_audio_op_decs)) {
DBG(vcp, "VOCS Vol Audio Descriptor found: handle 0x%04x",
value_handle);
vocs = vcp_get_vocs(vcp);
if (!vocs || vocs->voaodec)
return;
vocs->voaodec = attr;
vcp_read_value(vcp, value_handle, read_vocs_audio_descriptor,
vcp);
vcp->ao_dec_id = vcp_register_notify(vcp, value_handle,
vcp_audio_descriptor_notify, NULL);
}
}
static void read_aics_audio_ip_state(struct bt_vcp *vcp, bool success,
uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct aud_ip_st *ip_st;
struct iovec iov = {
.iov_base = (void *) value,
.iov_len = length,
};
if (!success) {
DBG(vcp, "Unable to read Audio Input State: error 0x%02x",
att_ecode);
return;
}
ip_st = util_iov_pull_mem(&iov, sizeof(*ip_st));
if (!ip_st) {
DBG(vcp, "Invalid Audio Input State");
return;
}
DBG(vcp, "Audio Input State, Gain Setting:%d", ip_st->gain_setting);
DBG(vcp, "Audio Input State, Mute:%x", ip_st->mute);
DBG(vcp, "Audio Input State, Gain Mode:%x", ip_st->gain_mode);
DBG(vcp, "Audio Input State, Change Counter:%x", ip_st->chg_counter);
}
static void aics_ip_state_notify(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *) value, .iov_len = length };
struct aud_ip_st *ip_st;
ip_st = util_iov_pull_mem(&iov, sizeof(*ip_st));
if (!ip_st) {
DBG(vcp, "Invalid Audio Input State");
return;
}
DBG(vcp, "Audio Input State, Gain Setting:%d", ip_st->gain_setting);
DBG(vcp, "Audio Input State, Mute:%x", ip_st->mute);
DBG(vcp, "Audio Input State, Gain Mode:%x", ip_st->gain_mode);
DBG(vcp, "Audio Input State, Change Counter:%x", ip_st->chg_counter);
}
static void read_aics_gain_setting_prop(struct bt_vcp *vcp, bool success,
uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *) value, .iov_len = length };
struct gain_setting_prop *aics_gain_setting_prop;
if (!success) {
DBG(vcp,
"Unable to read Gain Setting Properties Char: 0x%02x",
att_ecode);
return;
}
aics_gain_setting_prop = util_iov_pull_mem(&iov,
sizeof(*aics_gain_setting_prop));
if (!aics_gain_setting_prop) {
DBG(vcp, "Unable to get Gain Setting Properties Char");
return;
}
DBG(vcp, "Gain Setting Properties, Units: %x",
aics_gain_setting_prop->gain_setting_units);
DBG(vcp, "Gain Setting Properties, Min Value: %d",
aics_gain_setting_prop->gain_setting_min);
DBG(vcp, "Gain Setting Properties, Max Value: %d",
aics_gain_setting_prop->gain_setting_max);
}
static void read_aics_aud_ip_type(struct bt_vcp *vcp, bool success,
uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *) value, .iov_len = length };
uint8_t ip_type;
if (!success) {
DBG(vcp,
"Unable to read Audio Input Type Char: error 0x%02x",
att_ecode);
return;
}
if (!util_iov_pull_u8(&iov, &ip_type)) {
DBG(vcp, "Invalid Audio Input Type Char");
return;
}
DBG(vcp, "Audio Input Type : %x", ip_type);
}
static void read_aics_audio_ip_status(struct bt_vcp *vcp, bool success,
uint8_t att_ecode,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *) value, .iov_len = length };
uint8_t ip_status;
if (!success) {
DBG(vcp,
"Unable to read Audio Input Status Char: 0x%02x", att_ecode);
return;
}
if (!util_iov_pull_u8(&iov, &ip_status)) {
DBG(vcp, "Invalid Audio Input Status Char");
return;
}
DBG(vcp, "Audio Input Status : %x", ip_status);
}
static void aics_ip_status_notify(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value,
uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *) value, .iov_len = length };
uint8_t ip_status;
if (!util_iov_pull_u8(&iov, &ip_status)) {
DBG(vcp, "Invalid Audio Input Status Char");
return;
}
DBG(vcp, "Audio Input Status, %x", ip_status);
}
static void read_aics_audio_ip_description(struct bt_vcp *vcp, bool success,
uint8_t att_ecode,
const uint8_t *value,
uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *) value, .iov_len = length };
char *ip_descrptn;
if (!success) {
DBG(vcp,
"Unable to read Audio Input Description Char: error 0x%02x",
att_ecode);
return;
}
ip_descrptn = iov_pull_string(&iov);
if (!ip_descrptn)
return;
DBG(vcp, "Audio Input Description: %s", ip_descrptn);
free(ip_descrptn);
}
static void aics_audio_ip_desr_notify(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
{
struct iovec iov = { .iov_base = (void *) value, .iov_len = length };
char *aud_ip_desr;
aud_ip_desr = iov_pull_string(&iov);
if (!aud_ip_desr)
return;
DBG(vcp, "Audio Input Description Notify, %s", aud_ip_desr);
free(aud_ip_desr);
}
static void foreach_aics_char(struct gatt_db_attribute *attr, void *user_data)
{
struct bt_vcp *vcp = user_data;
uint16_t value_handle;
bt_uuid_t uuid, uuid_ipstate, uuid_gain_setting_prop, uuid_ip_type,
uuid_ip_status, uuid_ip_cp, uuid_ip_decs;
struct bt_aics *aics;
if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
NULL, NULL, &uuid))
return;
bt_uuid16_create(&uuid_ipstate, AICS_INPUT_STATE_CHAR_UUID);
bt_uuid16_create(&uuid_gain_setting_prop,
AICS_GAIN_SETTING_PROP_CHAR_UUID);
bt_uuid16_create(&uuid_ip_type, AICS_AUDIO_INPUT_TYPE_CHAR_UUID);
bt_uuid16_create(&uuid_ip_status, AICS_INPUT_STATUS_CHAR_UUID);
bt_uuid16_create(&uuid_ip_cp, AICS_AUDIO_INPUT_CP_CHRC_UUID);
bt_uuid16_create(&uuid_ip_decs, AICS_INPUT_DESCR_CHAR_UUID);
if (!bt_uuid_cmp(&uuid, &uuid_ipstate)) {
DBG(vcp,
"AICS Audio Input State Char found: handle 0x%04x",
value_handle);
aics = vcp_get_aics(vcp);
if (!aics || aics->aud_ip_state)
return;
aics->aud_ip_state = attr;
vcp_read_value(vcp, value_handle,
read_aics_audio_ip_state, vcp);
vcp->aics_ip_state_id = vcp_register_notify(vcp, value_handle,
aics_ip_state_notify, NULL);
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_gain_setting_prop)) {
DBG(vcp,
"AICS Gain Setting Properties Char found: handle 0x%04x",
value_handle);
aics = vcp_get_aics(vcp);
if (!aics || aics->gain_stting_prop)
return;
aics->gain_stting_prop = attr;
vcp_read_value(vcp, value_handle, read_aics_gain_setting_prop,
vcp);
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_ip_type)) {
DBG(vcp, "AICS Audio Input Type Char found: handle 0x%04x",
value_handle);
aics = vcp_get_aics(vcp);
if (!aics || aics->aud_ip_type)
return;
aics->aud_ip_type = attr;
vcp_read_value(vcp, value_handle, read_aics_aud_ip_type,
vcp);
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_ip_status)) {
DBG(vcp,
"AICS Audio Input Status Char found: handle 0x%04x",
value_handle);
aics = vcp_get_aics(vcp);
if (!aics || aics->aud_ip_status)
return;
aics->aud_ip_status = attr;
vcp_read_value(vcp, value_handle,
read_aics_audio_ip_status, vcp);
vcp->aics_ip_status_id = vcp_register_notify(vcp, value_handle,
aics_ip_status_notify, NULL);
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_ip_cp)) {
DBG(vcp, "AICS Input CP found: handle 0x%04x", value_handle);
aics = vcp_get_aics(vcp);
if (!aics || aics->aud_ip_cp)
return;
aics->aud_ip_cp = attr;
return;
}
if (!bt_uuid_cmp(&uuid, &uuid_ip_decs)) {
DBG(vcp,
"AICS Audio Input Description Char found: handle 0x%04x",
value_handle);
aics = vcp_get_aics(vcp);
if (!aics || aics->aud_ip_dscrptn)
return;
aics->aud_ip_dscrptn = attr;
vcp_read_value(vcp, value_handle,
read_aics_audio_ip_description, vcp);
vcp->aics_ip_descr_id = vcp_register_notify(vcp, value_handle,
aics_audio_ip_desr_notify, NULL);
}
}
static void foreach_vcs_service(struct gatt_db_attribute *attr,
void *user_data)
{
struct bt_vcp *vcp = user_data;
struct bt_vcs *vcs = vcp_get_vcs(vcp);
if (!vcs)
return;
vcs->service = attr;
gatt_db_service_set_claimed(attr, true);
gatt_db_service_foreach_char(attr, foreach_vcs_char, vcp);
}
static void foreach_vocs_service(struct gatt_db_attribute *attr,
void *user_data)
{
struct bt_vcp *vcp = user_data;
struct bt_vocs *vocs = vcp_get_vocs(vcp);
if (!vocs || !attr)
return;
vocs->service = attr;
gatt_db_service_set_claimed(attr, true);
gatt_db_service_foreach_char(attr, foreach_vocs_char, vcp);
}
static void foreach_aics_service(struct gatt_db_attribute *attr,
void *user_data)
{
struct bt_vcp *vcp = user_data;
struct bt_aics *aics = vcp_get_aics(vcp);
if (!aics || !attr)
return;
aics->service = attr;
gatt_db_service_set_claimed(attr, true);
gatt_db_service_foreach_char(attr, foreach_aics_char, vcp);
}
static void vcp_idle(void *data)
{
struct bt_vcp *vcp = data;
vcp->idle_id = 0;
if (vcp->ready_func)
vcp->ready_func(vcp, vcp->ready_data);
}
bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client,
bt_vcp_func_t ready, void *ready_user_data)
{
bt_uuid_t uuid;
if (!sessions)
sessions = queue_new();
queue_push_tail(sessions, vcp);
if (!client)
return true;
if (vcp->client)
return false;
vcp->client = bt_gatt_client_clone(client);
if (!vcp->client)
return false;
bt_uuid16_create(&uuid, VCS_UUID);
gatt_db_foreach_service(vcp->rdb->db, &uuid, foreach_vcs_service, vcp);
bt_uuid16_create(&uuid, VOL_OFFSET_CS_UUID);
gatt_db_foreach_service(vcp->rdb->db, &uuid, foreach_vocs_service, vcp);
bt_uuid16_create(&uuid, AUDIO_INPUT_CS_UUID);
gatt_db_foreach_service(vcp->rdb->db, &uuid, foreach_aics_service, vcp);
vcp->ready_func = ready;
vcp->ready_data = ready_user_data;
if (!vcp->idle_id)
vcp->idle_id = bt_gatt_client_idle_register(vcp->client,
vcp_idle, vcp, NULL);
return true;
}