linux-bluetooth.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH BlueZ v3 1/3] shared/vcp: Add initial code for handling VCP
@ 2022-09-19  8:07 Sathish Narasimman
  2022-09-19  8:07 ` [PATCH BlueZ v3 2/3] profiles: Add initial code for vcp plugin Sathish Narasimman
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Sathish Narasimman @ 2022-09-19  8:07 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Sathish Narasimman

This adds initial code for Volume Control Profile.
---
 Makefile.am      |    1 +
 src/shared/vcp.c | 1104 ++++++++++++++++++++++++++++++++++++++++++++++
 src/shared/vcp.h |   58 +++
 3 files changed, 1163 insertions(+)
 create mode 100644 src/shared/vcp.c
 create mode 100644 src/shared/vcp.h

diff --git a/Makefile.am b/Makefile.am
index 960bf21bc726..27715c73d76f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -231,6 +231,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
 			src/shared/gap.h src/shared/gap.c \
 			src/shared/log.h src/shared/log.c \
 			src/shared/bap.h src/shared/bap.c src/shared/ascs.h \
+			src/shared/vcp.c src/shared/vcp.h \
 			src/shared/lc3.h src/shared/tty.h
 
 if READLINE
diff --git a/src/shared/vcp.c b/src/shared/vcp.c
new file mode 100644
index 000000000000..944483c60622
--- /dev/null
+++ b/src/shared/vcp.c
@@ -0,0 +1,1104 @@
+// 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 "lib/bluetooth.h"
+#include "lib/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"
+#include "src/log.h"
+
+#define VCP_STEP_SIZE 1
+
+/* Apllication Error Code */
+#define BT_ATT_ERROR_INVALID_CHANGE_COUNTER	0x80
+#define BT_ATT_ERROR_OPCODE_NOT_SUPPORTED	0x81
+
+struct bt_vcp_db {
+	struct gatt_db *db;
+	struct bt_vcs *vcs;
+};
+
+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_vcs_ab_vol {
+	uint8_t	change_counter;
+	uint8_t	vol_set;
+} __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 {
+	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;
+
+	struct queue *pending;
+
+	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;
+};
+
+static struct queue *vcp_db;
+static struct queue *vcp_cbs;
+static struct queue *sessions;
+
+static void *iov_pull_mem(struct iovec *iov, size_t len)
+{
+	void *data = iov->iov_base;
+
+	if (iov->iov_len < len)
+		return NULL;
+
+	iov->iov_base += len;
+	iov->iov_len -= len;
+
+	return data;
+}
+
+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 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 void vcp_detached(void *data, void *user_data)
+{
+	struct bt_vcp_cb *cb = data;
+	struct bt_vcp *vcp = user_data;
+
+	cb->detached(vcp, cb->user_data);
+}
+
+void bt_vcp_detach(struct bt_vcp *vcp)
+{
+	if (!queue_remove(sessions, vcp))
+		return;
+
+	bt_gatt_client_unref(vcp->client);
+	vcp->client = NULL;
+
+	queue_foreach(vcp_cbs, vcp_detached, vcp);
+}
+
+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);
+}
+
+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_disconnected(int err, void *user_data)
+{
+	struct bt_vcp *vcp = user_data;
+
+	DBG("vcp %p disconnected err %d", vcp, err);
+
+	bt_vcp_detach(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;
+	}
+
+	vcp = bt_vcp_new(db, NULL);
+	vcp->att = att;
+
+	bt_att_register_disconnect(att, vcp_disconnected, vcp, NULL);
+
+	bt_vcp_attach(vcp, NULL);
+
+	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("");
+
+	vdb = vcp_get_vdb(vcp);
+	if (!vdb) {
+		DBG("error: VDB not availalbe");
+		return 0;
+	}
+
+	vstate = vdb_get_vstate(vdb);
+	if (!vstate) {
+		DBG("error: VSTATE not availalbe");
+		return 0;
+	}
+
+	change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+	if (!change_counter)
+		return 0;
+
+	if (*change_counter != vstate->counter) {
+		DBG("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*/
+
+	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("");
+
+	vdb = vcp_get_vdb(vcp);
+	if (!vdb) {
+		DBG("error: VDB not availalbe");
+		return 0;
+	}
+
+	vstate = vdb_get_vstate(vdb);
+	if (!vstate) {
+		DBG("error: VCP database not availalbe!!!!");
+		return 0;
+	}
+
+	change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+	if (!change_counter)
+		return 0;
+
+	if (*change_counter != vstate->counter) {
+		DBG("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*/
+
+	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("");
+
+	vdb = vcp_get_vdb(vcp);
+	if (!vdb) {
+		DBG("error: VDB not availalbe");
+		return 0;
+	}
+
+	vstate = vdb_get_vstate(vdb);
+	if (!vstate) {
+		DBG("error: VCP database not availalbe!!!!");
+		return 0;
+	}
+
+	change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+	if (!change_counter)
+		return 0;
+
+	if (*change_counter != vstate->counter) {
+		DBG("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*/
+
+	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("");
+
+	vdb = vcp_get_vdb(vcp);
+	if (!vdb) {
+		DBG("error: VDB not availalbe");
+		return 0;
+	}
+
+	vstate = vdb_get_vstate(vdb);
+	if (!vstate) {
+		DBG("error: VSTATE not availalbe");
+		return 0;
+	}
+
+	change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+	if (!change_counter)
+		return 0;
+
+	if (*change_counter != vstate->counter) {
+		DBG("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*/
+
+	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("");
+
+	vdb = vcp_get_vdb(vcp);
+	if (!vdb) {
+		DBG("error: VDB not availalbe");
+		return 0;
+	}
+
+	vstate = vdb_get_vstate(vdb);
+	if (!vstate) {
+		DBG("error: VSTATE not availalbe");
+		return 0;
+	}
+
+	req = iov_pull_mem(iov, sizeof(*req));
+	if (!req)
+		return 0;
+
+	if (req->change_counter != vstate->counter) {
+		DBG("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*/
+
+	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("");
+
+	vdb = vcp_get_vdb(vcp);
+	if (!vdb) {
+		DBG("error: VDB not availalbe");
+		return 0;
+	}
+
+	vstate = vdb_get_vstate(vdb);
+	if (!vstate) {
+		DBG("error: VSTATE not availalbe");
+		return 0;
+	}
+
+	change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+	if (!change_counter)
+		return 0;
+
+	if (*change_counter != vstate->counter) {
+		DBG("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("");
+
+	vdb = vcp_get_vdb(vcp);
+	if (!vdb) {
+		DBG("error: VDB not availalbe");
+		return 0;
+	}
+
+	vstate = vdb_get_vstate(vdb);
+	if (!vstate) {
+		DBG("error: VSTATE not availalbe");
+		return 0;
+	}
+
+	change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+	if (!change_counter)
+		return 0;
+
+	if (*change_counter != vstate->counter) {
+		DBG("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;
+}
+
+#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 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),
+	{}
+};
+
+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("");
+	if (offset) {
+		DBG("invalid offset %d", offset);
+		ret = BT_ATT_ERROR_INVALID_OFFSET;
+		goto respond;
+	}
+
+	if (len < sizeof(*vcp_op)) {
+		DBG("invalid len %ld < %ld sizeof(*param)", len,
+							sizeof(*vcp_op));
+		ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
+		goto respond;
+	}
+
+	vcp_op = iov_pull_mem(&iov, sizeof(*vcp_op));
+
+	DBG("vcp_op: %x", *vcp_op);
+
+	for (handler = vcp_handlers; handler && handler->str; handler++) {
+		if (handler->op != *vcp_op)
+			continue;
+
+		if (iov.iov_len < handler->size) {
+			DBG("invalid len %ld < %ld handler->size", len,
+			    handler->size);
+			ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
+			goto respond;
+		}
+
+		break;
+	}
+
+	if (handler && handler->str) {
+		DBG("%s", handler->str);
+
+		ret = handler->func(vcs, vcp, &iov);
+	} else {
+		DBG("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;
+
+	DBG("");
+
+	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 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;
+
+	DBG("vf: %x", vcs->vol_flag);
+
+	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 struct bt_vcs *vcs_new(struct gatt_db *db)
+{
+	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, 9);
+
+	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_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->vcs = vcs_new(db);
+	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);
+}
+
+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_vstate_register(uint16_t att_ecode, void *user_data)
+{
+	DBG("");
+	if (att_ecode)
+		DBG("VCS register failed: 0x%04x", att_ecode);
+}
+
+static void vcp_vflag_register(uint16_t att_ecode, void *user_data)
+{
+	DBG("");
+	if (att_ecode)
+		DBG("VCS register failed: 0x%04x", att_ecode);
+}
+
+static void vcp_vstate_notify(uint16_t value_handle, const uint8_t *value,
+				uint16_t length, void *user_data)
+{
+	struct vol_state vstate;
+
+	memcpy(&vstate, value, sizeof(struct vol_state));
+
+	DBG("Vol Settings 0x%x", vstate.vol_set);
+	DBG("Mute Status 0x%x", vstate.mute);
+	DBG("Vol Counter 0x%x", vstate.counter);
+}
+
+static void vcp_vflag_notify(uint16_t value_handle, const uint8_t *value,
+				uint16_t length, void *user_data)
+{
+	uint8_t vflag;
+
+	memcpy(&vflag, value, sizeof(vflag));
+
+	DBG("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)
+{
+	uint8_t *vol_flag;
+	struct iovec iov = {
+		.iov_base = (void *) value,
+		.iov_len = length,
+	};
+
+	if (!success) {
+		DBG("Unable to read VCP Vol State: error 0x%02x", att_ecode);
+		return;
+	}
+
+	vol_flag = iov_pull_mem(&iov, sizeof(*vol_flag));
+	if (!vol_flag) {
+		DBG("Unable to get Vol State");
+		return;
+	}
+
+	DBG("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("Unable to read VCP Vol State: error 0x%02x", att_ecode);
+		return;
+	}
+
+	vs = iov_pull_mem(&iov, sizeof(*vs));
+	if (!vs) {
+		DBG("Unable to get Vol State");
+		return;
+	}
+
+	DBG("Vol Set:%x", vs->vol_set);
+	DBG("Vol Mute:%x", vs->mute);
+	DBG("Vol Counter:%x", vs->counter);
+
+}
+
+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("Unable to send Read request");
+		free(pending);
+		return;
+	}
+
+	queue_push_tail(vcp->pending, pending);
+}
+
+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;
+
+	DBG("");
+	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("VCS Volume state found: handle 0x%04x", value_handle);
+
+		vcs = vcp_get_vcs(vcp);
+		if (!vcs || vcs->vs)
+			return;
+
+		vcs->vs = attr;
+
+		vcp_read_value(vcp, value_handle, read_vol_state, vcp);
+		vcp->vstate_id = bt_gatt_client_register_notify(vcp->client,
+						value_handle,
+						vcp_vstate_register,
+						vcp_vstate_notify, vcp, NULL);
+
+		return;
+	}
+
+	if (!bt_uuid_cmp(&uuid, &uuid_cp)) {
+		DBG("VCS Volume CP found: handle 0x%04x", value_handle);
+
+		vcs = vcp_get_vcs(vcp);
+		if (!vcs || vcs->vol_cp)
+			return;
+
+		vcs->vol_cp = attr;
+
+		return;
+	}
+
+	if (!bt_uuid_cmp(&uuid, &uuid_vflag)) {
+		DBG("VCS Vol Flaf found: handle 0x%04x", value_handle);
+
+		vcs = vcp_get_vcs(vcp);
+		if (!vcs || vcs->vf)
+			return;
+
+		vcs->vf = attr;
+
+		vcp_read_value(vcp, value_handle, read_vol_flag, vcp);
+		vcp->vflag_id = bt_gatt_client_register_notify(vcp->client,
+						value_handle,
+						vcp_vflag_register,
+						vcp_vflag_notify, vcp, 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);
+
+	DBG("");
+	vcs->service = attr;
+
+	gatt_db_service_set_claimed(attr, true);
+
+	gatt_db_service_foreach_char(attr, foreach_vcs_char, vcp);
+}
+
+bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client)
+{
+	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->ldb->db, &uuid, foreach_vcs_service, vcp);
+
+	return true;
+}
+
diff --git a/src/shared/vcp.h b/src/shared/vcp.h
new file mode 100644
index 000000000000..456ad8041162
--- /dev/null
+++ b/src/shared/vcp.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2020  Intel Corporation. All rights reserved.
+ *
+ */
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include "src/shared/io.h"
+
+#ifndef __packed
+#define __packed __attribute__((packed))
+#endif
+
+#define BT_VCP_RENDERER			0x01
+#define	BT_VCP_CONTROLLER		0x02
+
+#define BT_VCP_RELATIVE_VOL_DOWN	0x00
+#define BT_VCP_RELATIVE_VOL_UP		0x01
+#define BT_VCP_UNMUTE_RELATIVE_VOL_DOWN	0x02
+#define BT_VCP_UNMUTE_RELATIVE_VOL_UP	0x03
+#define BT_VCP_SET_ABOSULTE_VOL		0x04
+#define BT_VCP_UNMUTE			0x05
+#define BT_VCP_MUTE			0x06
+
+#ifndef MAX
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
+struct bt_vcp;
+
+typedef void (*bt_vcp_func_t)(struct bt_vcp *vcp, void *user_data);
+
+struct bt_vcp *bt_vcp_ref(struct bt_vcp *vcp);
+void bt_vcp_unref(struct bt_vcp *vcp);
+
+void bt_vcp_add_db(struct gatt_db *db);
+
+bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client);
+void bt_vcp_detach(struct bt_vcp *vcp);
+
+struct bt_att *bt_vcp_get_att(struct bt_vcp *vcp);
+
+bool bt_vcp_set_user_data(struct bt_vcp *vcp, void *user_data);
+
+/* Session related function */
+unsigned int bt_vcp_register(bt_vcp_func_t added, bt_vcp_func_t removed,
+							void *user_data);
+bool bt_vcp_unregister(unsigned int id);
+struct bt_vcp *bt_vcp_new(struct gatt_db *ldb, struct gatt_db *rdb);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH BlueZ v3 2/3] profiles: Add initial code for vcp plugin
  2022-09-19  8:07 [PATCH BlueZ v3 1/3] shared/vcp: Add initial code for handling VCP Sathish Narasimman
@ 2022-09-19  8:07 ` Sathish Narasimman
  2022-09-19  8:07 ` [PATCH BlueZ v3 3/3] monitor/att: Add decoding support for Volume Control Serice Sathish Narasimman
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 6+ messages in thread
From: Sathish Narasimman @ 2022-09-19  8:07 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Sathish Narasimman

This adds initial code for vcp plugin which handles Volume Control
Profile and Volume Control Service.
---
 Makefile.plugins     |   5 +
 configure.ac         |   4 +
 profiles/audio/vcp.c | 312 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 321 insertions(+)
 create mode 100644 profiles/audio/vcp.c

diff --git a/Makefile.plugins b/Makefile.plugins
index 213ed99edf2d..a3654980f86d 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -121,3 +121,8 @@ if BAP
 builtin_modules += bap
 builtin_sources += profiles/audio/bap.c
 endif
+
+if VCP
+builtin_modules += vcp
+builtin_sources += profiles/audio/vcp.c
+endif
diff --git a/configure.ac b/configure.ac
index 1f76915b4349..79645e6917cc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -199,6 +199,10 @@ AC_ARG_ENABLE(bap, AS_HELP_STRING([--disable-bap],
 		[disable BAP profile]), [enable_bap=${enableval}])
 AM_CONDITIONAL(BAP, test "${enable_bap}" != "no")
 
+AC_ARG_ENABLE(vcp, AS_HELP_STRING([--disable-vcp],
+		[disable VCP profile]), [enable_vcp=${enableval}])
+AM_CONDITIONAL(VCP, test "${enable_vcp}" != "no")
+
 AC_ARG_ENABLE(tools, AS_HELP_STRING([--disable-tools],
 		[disable Bluetooth tools]), [enable_tools=${enableval}])
 AM_CONDITIONAL(TOOLS, test "${enable_tools}" != "no")
diff --git a/profiles/audio/vcp.c b/profiles/audio/vcp.c
new file mode 100644
index 000000000000..34950d4070f2
--- /dev/null
+++ b/profiles/audio/vcp.c
@@ -0,0 +1,312 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2022  Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include "gdbus/gdbus.h"
+
+#include "lib/bluetooth.h"
+#include "lib/hci.h"
+#include "lib/sdp.h"
+#include "lib/uuid.h"
+
+#include "src/dbus-common.h"
+#include "src/shared/util.h"
+#include "src/shared/att.h"
+#include "src/shared/queue.h"
+#include "src/shared/gatt-db.h"
+#include "src/shared/gatt-client.h"
+#include "src/shared/gatt-server.h"
+#include "src/shared/vcp.h"
+
+#include "btio/btio.h"
+#include "src/plugin.h"
+#include "src/adapter.h"
+#include "src/gatt-database.h"
+#include "src/device.h"
+#include "src/profile.h"
+#include "src/service.h"
+#include "src/log.h"
+#include "src/error.h"
+
+#define VCS_UUID_STR "00001844-0000-1000-8000-00805f9b34fb"
+#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1"
+
+struct vcp_data {
+	struct btd_device *device;
+	struct btd_service *service;
+	struct bt_vcp *vcp;
+};
+
+static struct queue *sessions;
+
+static int vcp_disconnect(struct btd_service *service)
+{
+	DBG("");
+	return 0;
+}
+
+static struct vcp_data *vcp_data_new(struct btd_device *device)
+{
+	struct vcp_data *data;
+
+	data = new0(struct vcp_data, 1);
+	data->device = device;
+
+	return data;
+}
+
+static void vcp_data_add(struct vcp_data *data)
+{
+	DBG("data %p", data);
+
+	if (queue_find(sessions, NULL, data)) {
+		error("data %p already added", data);
+		return;
+	}
+
+	if (!sessions)
+		sessions = queue_new();
+
+	queue_push_tail(sessions, data);
+
+	if (data->service)
+		btd_service_set_user_data(data->service, data);
+}
+
+static bool match_data(const void *data, const void *match_data)
+{
+	const struct vcp_data *vdata = data;
+	const struct bt_vcp *vcp = match_data;
+
+	return vdata->vcp == vcp;
+}
+
+static void vcp_data_free(struct vcp_data *data)
+{
+	if (data->service) {
+		btd_service_set_user_data(data->service, NULL);
+		bt_vcp_set_user_data(data->vcp, NULL);
+	}
+
+	bt_vcp_unref(data->vcp);
+	free(data);
+}
+
+static void vcp_data_remove(struct vcp_data *data)
+{
+	DBG("data %p", data);
+
+	if (!queue_remove(sessions, data))
+		return;
+
+	vcp_data_free(data);
+
+	if (queue_isempty(sessions)) {
+		queue_destroy(sessions, NULL);
+		sessions = NULL;
+	}
+}
+
+static void vcp_detached(struct bt_vcp *vcp, void *user_data)
+{
+	struct vcp_data *data;
+
+	DBG("%p", vcp);
+
+	data = queue_find(sessions, match_data, vcp);
+	if (!data) {
+		error("Unable to find vcp session");
+		return;
+	}
+
+	vcp_data_remove(data);
+}
+
+static void vcp_attached(struct bt_vcp *vcp, void *user_data)
+{
+	struct vcp_data *data;
+	struct bt_att *att;
+	struct btd_device *device;
+
+	DBG("%p", vcp);
+
+	data = queue_find(sessions, match_data, vcp);
+	if (data)
+		return;
+
+	att = bt_vcp_get_att(vcp);
+	if (!att)
+		return;
+
+	device = btd_adapter_find_device_by_fd(bt_att_get_fd(att));
+	if (!device) {
+		error("Unable to find device");
+		return;
+	}
+
+	data = vcp_data_new(device);
+	data->vcp = vcp;
+
+	vcp_data_add(data);
+
+}
+
+static int vcp_probe(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	struct btd_adapter *adapter = device_get_adapter(device);
+	struct btd_gatt_database *database = btd_adapter_get_database(adapter);
+	struct vcp_data *data = btd_service_get_user_data(service);
+	char addr[18];
+
+	ba2str(device_get_address(device), addr);
+	DBG("%s", addr);
+
+	/* Ignore, if we were probed for this device already */
+	if (data) {
+		error("Profile probed twice for the same device!");
+		return -EINVAL;
+	}
+
+	data = vcp_data_new(device);
+	data->service = service;
+
+	data->vcp = bt_vcp_new(btd_gatt_database_get_db(database),
+					btd_device_get_gatt_db(device));
+	if (!data->vcp) {
+		error("Unable to create VCP instance");
+		free(data);
+		return -EINVAL;
+	}
+
+	vcp_data_add(data);
+
+	bt_vcp_set_user_data(data->vcp, service);
+
+	return 0;
+}
+
+static void vcp_remove(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	struct vcp_data *data;
+	char addr[18];
+
+	ba2str(device_get_address(device), addr);
+	DBG("%s", addr);
+
+	data = btd_service_get_user_data(service);
+	if (!data) {
+		error("VCP service not handled by profile");
+		return;
+	}
+
+	vcp_data_remove(data);
+}
+
+static int vcp_accept(struct btd_service *service)
+{
+	struct btd_device *device = btd_service_get_device(service);
+	struct bt_gatt_client *client = btd_device_get_gatt_client(device);
+	struct vcp_data *data = btd_service_get_user_data(service);
+	char addr[18];
+
+	ba2str(device_get_address(device), addr);
+	DBG("%s", addr);
+
+	if (!data) {
+		error("VCP service not handled by profile");
+		return -EINVAL;
+	}
+
+	if (!bt_vcp_attach(data->vcp, client)) {
+		error("VCP unable to attach");
+		return -EINVAL;
+	}
+
+	btd_service_connecting_complete(service, 0);
+
+	return 0;
+}
+
+static int vcp_server_probe(struct btd_profile *p,
+				  struct btd_adapter *adapter)
+{
+	struct btd_gatt_database *database = btd_adapter_get_database(adapter);
+
+	DBG("VCP path %s", adapter_get_path(adapter));
+
+	bt_vcp_add_db(btd_gatt_database_get_db(database));
+
+	return 0;
+}
+
+static void vcp_server_remove(struct btd_profile *p,
+					struct btd_adapter *adapter)
+{
+	DBG("VCP remove Adapter");
+}
+
+static struct btd_profile vcp_profile = {
+	.name		= "vcp",
+	.priority	= BTD_PROFILE_PRIORITY_MEDIUM,
+	.remote_uuid	= VCS_UUID_STR,
+
+	.device_probe	= vcp_probe,
+	.device_remove	= vcp_remove,
+
+	.accept		= vcp_accept,
+	.disconnect	= vcp_disconnect,
+
+	.adapter_probe = vcp_server_probe,
+	.adapter_remove = vcp_server_remove,
+};
+
+static unsigned int vcp_id = 0;
+
+static int vcp_init(void)
+{
+	if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) {
+		warn("D-Bus experimental not enabled");
+		return -ENOTSUP;
+	}
+
+	btd_profile_register(&vcp_profile);
+	vcp_id = bt_vcp_register(vcp_attached, vcp_detached, NULL);
+
+	return 0;
+}
+
+static void vcp_exit(void)
+{
+	if (g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL) {
+		btd_profile_unregister(&vcp_profile);
+		bt_vcp_unregister(vcp_id);
+	}
+}
+
+BLUETOOTH_PLUGIN_DEFINE(vcp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
+							vcp_init, vcp_exit)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH BlueZ v3 3/3] monitor/att: Add decoding support for Volume Control Serice
  2022-09-19  8:07 [PATCH BlueZ v3 1/3] shared/vcp: Add initial code for handling VCP Sathish Narasimman
  2022-09-19  8:07 ` [PATCH BlueZ v3 2/3] profiles: Add initial code for vcp plugin Sathish Narasimman
@ 2022-09-19  8:07 ` Sathish Narasimman
  2022-09-19  9:36 ` [BlueZ,v3,1/3] shared/vcp: Add initial code for handling VCP bluez.test.bot
  2022-09-19 22:35 ` [PATCH BlueZ v3 1/3] " Luiz Augusto von Dentz
  3 siblings, 0 replies; 6+ messages in thread
From: Sathish Narasimman @ 2022-09-19  8:07 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Sathish Narasimman

This adds decoding support for VCS attributes

> ACL Data RX: Handle 3585 flags 0x02 dlen 7
      ATT: Read Request (0x0a) len 2
        Handle: 0x0017 Type: Volume State (0x2b7d)
< ACL Data TX: Handle 3585 flags 0x00 dlen 8
      ATT: Read Response (0x0b) len 3
        Value: 000000
        Handle: 0x0017 Type: Volume State (0x2b7d)
            Volume Setting: 0
            Not Muted: 0
            Change Counter: 0
> HCI Event: Number of Completed Packets (0x13) plen 5
        Num handles: 1
        Handle: 3585 Address: 49:71:FC:C0:66:C6 (Resolvable)
        Count: 1
> ACL Data RX: Handle 3585 flags 0x02 dlen 7
      ATT: Read Request (0x0a) len 2
        Handle: 0x001c Type: Volume Flags (0x2b7f)
< ACL Data TX: Handle 3585 flags 0x00 dlen 6
      ATT: Read Response (0x0b) len 1
        Value: 01
        Handle: 0x001c Type: Volume Flags (0x2b7f)
            Volume Falg: 1
---
 monitor/att.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 159 insertions(+)

diff --git a/monitor/att.c b/monitor/att.c
index b7470f7a2ff4..3c1ff2e2aaa0 100644
--- a/monitor/att.c
+++ b/monitor/att.c
@@ -1590,6 +1590,162 @@ static void pac_context_notify(const struct l2cap_frame *frame)
 	print_pac_context(frame);
 }
 
+static void print_vcs_state(const struct l2cap_frame *frame)
+{
+	uint8_t vol_set, mute, chng_ctr;
+
+	if (!l2cap_frame_get_u8((void *)frame, &vol_set)) {
+		print_text(COLOR_ERROR, "Volume Settings: invalid size");
+		goto done;
+	}
+	print_field("    Volume Setting: %u", vol_set);
+
+	if (!l2cap_frame_get_u8((void *)frame, &mute)) {
+		print_text(COLOR_ERROR, "Mute Filed: invalid size");
+		goto done;
+	}
+
+	switch (mute) {
+	case 0x00:
+		print_field("    Not Muted: %u", mute);
+		break;
+	case 0x01:
+		print_field("    Muted: %u", mute);
+		break;
+	default:
+		print_field("    Unknown Mute Value: %u", mute);
+		break;
+	}
+
+	if (!l2cap_frame_get_u8((void *)frame, &chng_ctr)) {
+		print_text(COLOR_ERROR, "Change Counter: invalid size");
+		goto done;
+	}
+	print_field("    Change Counter: %u", chng_ctr);
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void vol_state_read(const struct l2cap_frame *frame)
+{
+	print_vcs_state(frame);
+}
+
+static void vol_state_notify(const struct l2cap_frame *frame)
+{
+	print_vcs_state(frame);
+}
+
+static bool vcs_config_cmd(const struct l2cap_frame *frame)
+{
+	if (!l2cap_frame_print_u8((void *)frame, "    Change Counter"))
+		return false;
+
+	return true;
+}
+
+static bool vcs_absolute_cmd(const struct l2cap_frame *frame)
+{
+	if (!l2cap_frame_print_u8((void *)frame, "    Change Counter"))
+		return false;
+
+	if (!l2cap_frame_print_u8((void *)frame, "    Volume Setting"))
+		return false;
+
+	return true;
+}
+
+#define ASE_CMD(_op, _desc, _func) \
+[_op] = { \
+	.desc = _desc, \
+	.func = _func, \
+}
+
+struct vcs_cmd {
+	const char *desc;
+	bool (*func)(const struct l2cap_frame *frame);
+} vcs_cmd_table[] = {
+	/* Opcode = 0x00 (Relative Volume Down) */
+	ASE_CMD(0x00, "Relative Volume Down", vcs_config_cmd),
+	/* Opcode = 0x01 (Relative Volume Up) */
+	ASE_CMD(0x01, "Relative Volume Up", vcs_config_cmd),
+	/* Opcode = 0x02 (Unmute/Relative Volume Down) */
+	ASE_CMD(0x02, "Unmute/Relative Volume Down", vcs_config_cmd),
+	/* Opcode = 0x03 (Unmute/Relative Volume Up) */
+	ASE_CMD(0x03, "Unmute/Relative Volume Up", vcs_config_cmd),
+	/* Opcode = 0x04 (Set Absolute Volume) */
+	ASE_CMD(0x04, "Set Absolute Volume", vcs_absolute_cmd),
+	/* Opcode = 0x05 (Unmute) */
+	ASE_CMD(0x05, "Unmute", vcs_config_cmd),
+	/* Opcode = 0x06 (Mute) */
+	ASE_CMD(0x06, "Mute", vcs_config_cmd),
+};
+
+static struct vcs_cmd *vcs_get_cmd(uint8_t op)
+{
+	if (op > ARRAY_SIZE(vcs_cmd_table))
+		return NULL;
+
+	return &vcs_cmd_table[op];
+}
+
+static void print_vcs_cmd(const struct l2cap_frame *frame)
+{
+	uint8_t op;
+	struct vcs_cmd *cmd;
+
+	if (!l2cap_frame_get_u8((void *)frame, &op)) {
+		print_text(COLOR_ERROR, "opcode: invalid size");
+		goto done;
+	}
+
+	cmd = vcs_get_cmd(op);
+	if (!cmd) {
+		print_field("    Opcode: Reserved (0x%2.2x)", op);
+		goto done;
+	}
+
+	print_field("    Opcode: %s (0x%2.2x)", cmd->desc, op);
+	if (!cmd->func(frame))
+		print_field("    Unknown Opcode");
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void vol_cp_write(const struct l2cap_frame *frame)
+{
+	print_vcs_cmd(frame);
+}
+
+static void print_vcs_flag(const struct l2cap_frame *frame)
+{
+	uint8_t vol_flag;
+
+	if (!l2cap_frame_get_u8((void *)frame, &vol_flag)) {
+		print_text(COLOR_ERROR, "Volume Flag: invalid size");
+		goto done;
+	}
+	print_field("    Volume Falg: %u", vol_flag);
+
+done:
+	if (frame->size)
+		print_hex_field("  Data", frame->data, frame->size);
+}
+
+static void vol_flag_read(const struct l2cap_frame *frame)
+{
+	print_vcs_flag(frame);
+}
+
+static void vol_flag_notify(const struct l2cap_frame *frame)
+{
+	print_vcs_flag(frame);
+}
+
 #define GATT_HANDLER(_uuid, _read, _write, _notify) \
 { \
 	.uuid = { \
@@ -1617,6 +1773,9 @@ struct gatt_handler {
 	GATT_HANDLER(0x2bcc, pac_loc_read, NULL, pac_loc_notify),
 	GATT_HANDLER(0x2bcd, pac_context_read, NULL, pac_context_notify),
 	GATT_HANDLER(0x2bce, pac_context_read, NULL, pac_context_notify),
+	GATT_HANDLER(0x2b7d, vol_state_read, NULL, vol_state_notify),
+	GATT_HANDLER(0x2b7e, NULL, vol_cp_write, NULL),
+	GATT_HANDLER(0x2b7f, vol_flag_read, NULL, vol_flag_notify),
 };
 
 static struct gatt_handler *get_handler(struct gatt_db_attribute *attr)
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* RE: [BlueZ,v3,1/3] shared/vcp: Add initial code for handling VCP
  2022-09-19  8:07 [PATCH BlueZ v3 1/3] shared/vcp: Add initial code for handling VCP Sathish Narasimman
  2022-09-19  8:07 ` [PATCH BlueZ v3 2/3] profiles: Add initial code for vcp plugin Sathish Narasimman
  2022-09-19  8:07 ` [PATCH BlueZ v3 3/3] monitor/att: Add decoding support for Volume Control Serice Sathish Narasimman
@ 2022-09-19  9:36 ` bluez.test.bot
  2022-09-19 22:35 ` [PATCH BlueZ v3 1/3] " Luiz Augusto von Dentz
  3 siblings, 0 replies; 6+ messages in thread
From: bluez.test.bot @ 2022-09-19  9:36 UTC (permalink / raw)
  To: linux-bluetooth, sathish.narasimman

[-- Attachment #1: Type: text/plain, Size: 2786 bytes --]

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=678079

---Test result---

Test Summary:
CheckPatch                    FAIL      3.51 seconds
GitLint                       PASS      1.42 seconds
Prep - Setup ELL              PASS      34.86 seconds
Build - Prep                  PASS      0.89 seconds
Build - Configure             PASS      10.66 seconds
Build - Make                  PASS      1181.14 seconds
Make Check                    PASS      12.57 seconds
Make Check w/Valgrind         PASS      368.00 seconds
Make Distcheck                PASS      317.23 seconds
Build w/ext ELL - Configure   PASS      11.08 seconds
Build w/ext ELL - Make        PASS      110.20 seconds
Incremental Build w/ patches  PASS      392.00 seconds
Scan Build                    PASS      806.14 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script with rule in .checkpatch.conf
Output:
[BlueZ,v3,1/3] shared/vcp: Add initial code for handling VCP
WARNING:PREFER_DEFINED_ATTRIBUTE_MACRO: Prefer __packed over __attribute__((packed))
#1215: FILE: src/shared/vcp.h:16:
+#define __packed __attribute__((packed))

/github/workspace/src/12979747.patch total: 0 errors, 1 warnings, 1169 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/12979747.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.

[BlueZ,v3,2/3] profiles: Add initial code for vcp plugin
ERROR:INITIALISED_STATIC: do not initialise statics to 0
#395: FILE: profiles/audio/vcp.c:288:
+static unsigned int vcp_id = 0;

/github/workspace/src/12979749.patch total: 1 errors, 0 warnings, 330 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/12979749.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.




---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH BlueZ v3 1/3] shared/vcp: Add initial code for handling VCP
  2022-09-19  8:07 [PATCH BlueZ v3 1/3] shared/vcp: Add initial code for handling VCP Sathish Narasimman
                   ` (2 preceding siblings ...)
  2022-09-19  9:36 ` [BlueZ,v3,1/3] shared/vcp: Add initial code for handling VCP bluez.test.bot
@ 2022-09-19 22:35 ` Luiz Augusto von Dentz
  2022-09-20 10:26   ` Sathish Narasimman
  3 siblings, 1 reply; 6+ messages in thread
From: Luiz Augusto von Dentz @ 2022-09-19 22:35 UTC (permalink / raw)
  To: Sathish Narasimman; +Cc: linux-bluetooth

Hi Sathish,

On Mon, Sep 19, 2022 at 1:07 AM Sathish Narasimman
<sathish.narasimman@intel.com> wrote:
>
> This adds initial code for Volume Control Profile.
> ---
>  Makefile.am      |    1 +
>  src/shared/vcp.c | 1104 ++++++++++++++++++++++++++++++++++++++++++++++
>  src/shared/vcp.h |   58 +++
>  3 files changed, 1163 insertions(+)
>  create mode 100644 src/shared/vcp.c
>  create mode 100644 src/shared/vcp.h
>
> diff --git a/Makefile.am b/Makefile.am
> index 960bf21bc726..27715c73d76f 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -231,6 +231,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
>                         src/shared/gap.h src/shared/gap.c \
>                         src/shared/log.h src/shared/log.c \
>                         src/shared/bap.h src/shared/bap.c src/shared/ascs.h \
> +                       src/shared/vcp.c src/shared/vcp.h \
>                         src/shared/lc3.h src/shared/tty.h
>
>  if READLINE
> diff --git a/src/shared/vcp.c b/src/shared/vcp.c
> new file mode 100644
> index 000000000000..944483c60622
> --- /dev/null
> +++ b/src/shared/vcp.c
> @@ -0,0 +1,1104 @@
> +// 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 "lib/bluetooth.h"
> +#include "lib/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"
> +#include "src/log.h"

I went ahead and applied this as is but you need to fix using log.h,
that won't work when we would be doing unit tests which don't link
with the daemon, that is why we have set_debug functions for other
instances, so please fix it.

> +#define VCP_STEP_SIZE 1
> +
> +/* Apllication Error Code */
> +#define BT_ATT_ERROR_INVALID_CHANGE_COUNTER    0x80
> +#define BT_ATT_ERROR_OPCODE_NOT_SUPPORTED      0x81
> +
> +struct bt_vcp_db {
> +       struct gatt_db *db;
> +       struct bt_vcs *vcs;
> +};
> +
> +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_vcs_ab_vol {
> +       uint8_t change_counter;
> +       uint8_t vol_set;
> +} __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 {
> +       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;
> +
> +       struct queue *pending;
> +
> +       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;
> +};
> +
> +static struct queue *vcp_db;
> +static struct queue *vcp_cbs;
> +static struct queue *sessions;
> +
> +static void *iov_pull_mem(struct iovec *iov, size_t len)
> +{
> +       void *data = iov->iov_base;
> +
> +       if (iov->iov_len < len)
> +               return NULL;
> +
> +       iov->iov_base += len;
> +       iov->iov_len -= len;
> +
> +       return data;
> +}
> +
> +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 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 void vcp_detached(void *data, void *user_data)
> +{
> +       struct bt_vcp_cb *cb = data;
> +       struct bt_vcp *vcp = user_data;
> +
> +       cb->detached(vcp, cb->user_data);
> +}
> +
> +void bt_vcp_detach(struct bt_vcp *vcp)
> +{
> +       if (!queue_remove(sessions, vcp))
> +               return;
> +
> +       bt_gatt_client_unref(vcp->client);
> +       vcp->client = NULL;
> +
> +       queue_foreach(vcp_cbs, vcp_detached, vcp);
> +}
> +
> +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);
> +}
> +
> +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_disconnected(int err, void *user_data)
> +{
> +       struct bt_vcp *vcp = user_data;
> +
> +       DBG("vcp %p disconnected err %d", vcp, err);
> +
> +       bt_vcp_detach(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;
> +       }
> +
> +       vcp = bt_vcp_new(db, NULL);
> +       vcp->att = att;
> +
> +       bt_att_register_disconnect(att, vcp_disconnected, vcp, NULL);
> +
> +       bt_vcp_attach(vcp, NULL);
> +
> +       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("");
> +
> +       vdb = vcp_get_vdb(vcp);
> +       if (!vdb) {
> +               DBG("error: VDB not availalbe");
> +               return 0;
> +       }
> +
> +       vstate = vdb_get_vstate(vdb);
> +       if (!vstate) {
> +               DBG("error: VSTATE not availalbe");
> +               return 0;
> +       }
> +
> +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> +       if (!change_counter)
> +               return 0;
> +
> +       if (*change_counter != vstate->counter) {
> +               DBG("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*/
> +
> +       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("");
> +
> +       vdb = vcp_get_vdb(vcp);
> +       if (!vdb) {
> +               DBG("error: VDB not availalbe");
> +               return 0;
> +       }
> +
> +       vstate = vdb_get_vstate(vdb);
> +       if (!vstate) {
> +               DBG("error: VCP database not availalbe!!!!");
> +               return 0;
> +       }
> +
> +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> +       if (!change_counter)
> +               return 0;
> +
> +       if (*change_counter != vstate->counter) {
> +               DBG("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*/
> +
> +       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("");
> +
> +       vdb = vcp_get_vdb(vcp);
> +       if (!vdb) {
> +               DBG("error: VDB not availalbe");
> +               return 0;
> +       }
> +
> +       vstate = vdb_get_vstate(vdb);
> +       if (!vstate) {
> +               DBG("error: VCP database not availalbe!!!!");
> +               return 0;
> +       }
> +
> +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> +       if (!change_counter)
> +               return 0;
> +
> +       if (*change_counter != vstate->counter) {
> +               DBG("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*/
> +
> +       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("");
> +
> +       vdb = vcp_get_vdb(vcp);
> +       if (!vdb) {
> +               DBG("error: VDB not availalbe");
> +               return 0;
> +       }
> +
> +       vstate = vdb_get_vstate(vdb);
> +       if (!vstate) {
> +               DBG("error: VSTATE not availalbe");
> +               return 0;
> +       }
> +
> +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> +       if (!change_counter)
> +               return 0;
> +
> +       if (*change_counter != vstate->counter) {
> +               DBG("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*/
> +
> +       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("");
> +
> +       vdb = vcp_get_vdb(vcp);
> +       if (!vdb) {
> +               DBG("error: VDB not availalbe");
> +               return 0;
> +       }
> +
> +       vstate = vdb_get_vstate(vdb);
> +       if (!vstate) {
> +               DBG("error: VSTATE not availalbe");
> +               return 0;
> +       }
> +
> +       req = iov_pull_mem(iov, sizeof(*req));
> +       if (!req)
> +               return 0;
> +
> +       if (req->change_counter != vstate->counter) {
> +               DBG("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*/
> +
> +       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("");
> +
> +       vdb = vcp_get_vdb(vcp);
> +       if (!vdb) {
> +               DBG("error: VDB not availalbe");
> +               return 0;
> +       }
> +
> +       vstate = vdb_get_vstate(vdb);
> +       if (!vstate) {
> +               DBG("error: VSTATE not availalbe");
> +               return 0;
> +       }
> +
> +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> +       if (!change_counter)
> +               return 0;
> +
> +       if (*change_counter != vstate->counter) {
> +               DBG("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("");
> +
> +       vdb = vcp_get_vdb(vcp);
> +       if (!vdb) {
> +               DBG("error: VDB not availalbe");
> +               return 0;
> +       }
> +
> +       vstate = vdb_get_vstate(vdb);
> +       if (!vstate) {
> +               DBG("error: VSTATE not availalbe");
> +               return 0;
> +       }
> +
> +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> +       if (!change_counter)
> +               return 0;
> +
> +       if (*change_counter != vstate->counter) {
> +               DBG("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;
> +}
> +
> +#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 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),
> +       {}
> +};
> +
> +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("");
> +       if (offset) {
> +               DBG("invalid offset %d", offset);
> +               ret = BT_ATT_ERROR_INVALID_OFFSET;
> +               goto respond;
> +       }
> +
> +       if (len < sizeof(*vcp_op)) {
> +               DBG("invalid len %ld < %ld sizeof(*param)", len,
> +                                                       sizeof(*vcp_op));
> +               ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
> +               goto respond;
> +       }
> +
> +       vcp_op = iov_pull_mem(&iov, sizeof(*vcp_op));
> +
> +       DBG("vcp_op: %x", *vcp_op);
> +
> +       for (handler = vcp_handlers; handler && handler->str; handler++) {
> +               if (handler->op != *vcp_op)
> +                       continue;
> +
> +               if (iov.iov_len < handler->size) {
> +                       DBG("invalid len %ld < %ld handler->size", len,
> +                           handler->size);
> +                       ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
> +                       goto respond;
> +               }
> +
> +               break;
> +       }
> +
> +       if (handler && handler->str) {
> +               DBG("%s", handler->str);
> +
> +               ret = handler->func(vcs, vcp, &iov);
> +       } else {
> +               DBG("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;
> +
> +       DBG("");
> +
> +       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 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;
> +
> +       DBG("vf: %x", vcs->vol_flag);
> +
> +       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 struct bt_vcs *vcs_new(struct gatt_db *db)
> +{
> +       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, 9);
> +
> +       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_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->vcs = vcs_new(db);
> +       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);
> +}
> +
> +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_vstate_register(uint16_t att_ecode, void *user_data)
> +{
> +       DBG("");
> +       if (att_ecode)
> +               DBG("VCS register failed: 0x%04x", att_ecode);
> +}
> +
> +static void vcp_vflag_register(uint16_t att_ecode, void *user_data)
> +{
> +       DBG("");
> +       if (att_ecode)
> +               DBG("VCS register failed: 0x%04x", att_ecode);
> +}
> +
> +static void vcp_vstate_notify(uint16_t value_handle, const uint8_t *value,
> +                               uint16_t length, void *user_data)
> +{
> +       struct vol_state vstate;
> +
> +       memcpy(&vstate, value, sizeof(struct vol_state));
> +
> +       DBG("Vol Settings 0x%x", vstate.vol_set);
> +       DBG("Mute Status 0x%x", vstate.mute);
> +       DBG("Vol Counter 0x%x", vstate.counter);
> +}
> +
> +static void vcp_vflag_notify(uint16_t value_handle, const uint8_t *value,
> +                               uint16_t length, void *user_data)
> +{
> +       uint8_t vflag;
> +
> +       memcpy(&vflag, value, sizeof(vflag));
> +
> +       DBG("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)
> +{
> +       uint8_t *vol_flag;
> +       struct iovec iov = {
> +               .iov_base = (void *) value,
> +               .iov_len = length,
> +       };
> +
> +       if (!success) {
> +               DBG("Unable to read VCP Vol State: error 0x%02x", att_ecode);
> +               return;
> +       }
> +
> +       vol_flag = iov_pull_mem(&iov, sizeof(*vol_flag));
> +       if (!vol_flag) {
> +               DBG("Unable to get Vol State");
> +               return;
> +       }
> +
> +       DBG("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("Unable to read VCP Vol State: error 0x%02x", att_ecode);
> +               return;
> +       }
> +
> +       vs = iov_pull_mem(&iov, sizeof(*vs));
> +       if (!vs) {
> +               DBG("Unable to get Vol State");
> +               return;
> +       }
> +
> +       DBG("Vol Set:%x", vs->vol_set);
> +       DBG("Vol Mute:%x", vs->mute);
> +       DBG("Vol Counter:%x", vs->counter);
> +
> +}
> +
> +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("Unable to send Read request");
> +               free(pending);
> +               return;
> +       }
> +
> +       queue_push_tail(vcp->pending, pending);
> +}
> +
> +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;
> +
> +       DBG("");
> +       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("VCS Volume state found: handle 0x%04x", value_handle);
> +
> +               vcs = vcp_get_vcs(vcp);
> +               if (!vcs || vcs->vs)
> +                       return;
> +
> +               vcs->vs = attr;
> +
> +               vcp_read_value(vcp, value_handle, read_vol_state, vcp);
> +               vcp->vstate_id = bt_gatt_client_register_notify(vcp->client,
> +                                               value_handle,
> +                                               vcp_vstate_register,
> +                                               vcp_vstate_notify, vcp, NULL);
> +
> +               return;
> +       }
> +
> +       if (!bt_uuid_cmp(&uuid, &uuid_cp)) {
> +               DBG("VCS Volume CP found: handle 0x%04x", value_handle);
> +
> +               vcs = vcp_get_vcs(vcp);
> +               if (!vcs || vcs->vol_cp)
> +                       return;
> +
> +               vcs->vol_cp = attr;
> +
> +               return;
> +       }
> +
> +       if (!bt_uuid_cmp(&uuid, &uuid_vflag)) {
> +               DBG("VCS Vol Flaf found: handle 0x%04x", value_handle);
> +
> +               vcs = vcp_get_vcs(vcp);
> +               if (!vcs || vcs->vf)
> +                       return;
> +
> +               vcs->vf = attr;
> +
> +               vcp_read_value(vcp, value_handle, read_vol_flag, vcp);
> +               vcp->vflag_id = bt_gatt_client_register_notify(vcp->client,
> +                                               value_handle,
> +                                               vcp_vflag_register,
> +                                               vcp_vflag_notify, vcp, 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);
> +
> +       DBG("");
> +       vcs->service = attr;
> +
> +       gatt_db_service_set_claimed(attr, true);
> +
> +       gatt_db_service_foreach_char(attr, foreach_vcs_char, vcp);
> +}
> +
> +bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client)
> +{
> +       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->ldb->db, &uuid, foreach_vcs_service, vcp);
> +
> +       return true;
> +}
> +
> diff --git a/src/shared/vcp.h b/src/shared/vcp.h
> new file mode 100644
> index 000000000000..456ad8041162
> --- /dev/null
> +++ b/src/shared/vcp.h
> @@ -0,0 +1,58 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + *
> + *  BlueZ - Bluetooth protocol stack for Linux
> + *
> + *  Copyright (C) 2020  Intel Corporation. All rights reserved.
> + *
> + */
> +
> +#include <stdbool.h>
> +#include <inttypes.h>
> +
> +#include "src/shared/io.h"
> +
> +#ifndef __packed
> +#define __packed __attribute__((packed))
> +#endif
> +
> +#define BT_VCP_RENDERER                        0x01
> +#define        BT_VCP_CONTROLLER               0x02
> +
> +#define BT_VCP_RELATIVE_VOL_DOWN       0x00
> +#define BT_VCP_RELATIVE_VOL_UP         0x01
> +#define BT_VCP_UNMUTE_RELATIVE_VOL_DOWN        0x02
> +#define BT_VCP_UNMUTE_RELATIVE_VOL_UP  0x03
> +#define BT_VCP_SET_ABOSULTE_VOL                0x04
> +#define BT_VCP_UNMUTE                  0x05
> +#define BT_VCP_MUTE                    0x06
> +
> +#ifndef MAX
> +#define MAX(a, b) ((a) > (b) ? (a) : (b))
> +#endif
> +
> +#ifndef MIN
> +#define MIN(a, b) ((a) < (b) ? (a) : (b))
> +#endif
> +
> +struct bt_vcp;
> +
> +typedef void (*bt_vcp_func_t)(struct bt_vcp *vcp, void *user_data);
> +
> +struct bt_vcp *bt_vcp_ref(struct bt_vcp *vcp);
> +void bt_vcp_unref(struct bt_vcp *vcp);
> +
> +void bt_vcp_add_db(struct gatt_db *db);
> +
> +bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client);
> +void bt_vcp_detach(struct bt_vcp *vcp);
> +
> +struct bt_att *bt_vcp_get_att(struct bt_vcp *vcp);
> +
> +bool bt_vcp_set_user_data(struct bt_vcp *vcp, void *user_data);
> +
> +/* Session related function */
> +unsigned int bt_vcp_register(bt_vcp_func_t added, bt_vcp_func_t removed,
> +                                                       void *user_data);
> +bool bt_vcp_unregister(unsigned int id);
> +struct bt_vcp *bt_vcp_new(struct gatt_db *ldb, struct gatt_db *rdb);
> --
> 2.25.1
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH BlueZ v3 1/3] shared/vcp: Add initial code for handling VCP
  2022-09-19 22:35 ` [PATCH BlueZ v3 1/3] " Luiz Augusto von Dentz
@ 2022-09-20 10:26   ` Sathish Narasimman
  0 siblings, 0 replies; 6+ messages in thread
From: Sathish Narasimman @ 2022-09-20 10:26 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: Sathish Narasimman, linux-bluetooth

Hi Luiz

On Tue, Sep 20, 2022 at 4:41 AM Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
>
> Hi Sathish,
>
> On Mon, Sep 19, 2022 at 1:07 AM Sathish Narasimman
> <sathish.narasimman@intel.com> wrote:
> >
> > This adds initial code for Volume Control Profile.
> > ---
> >  Makefile.am      |    1 +
> >  src/shared/vcp.c | 1104 ++++++++++++++++++++++++++++++++++++++++++++++
> >  src/shared/vcp.h |   58 +++
> >  3 files changed, 1163 insertions(+)
> >  create mode 100644 src/shared/vcp.c
> >  create mode 100644 src/shared/vcp.h
> >
> > diff --git a/Makefile.am b/Makefile.am
> > index 960bf21bc726..27715c73d76f 100644
> > --- a/Makefile.am
> > +++ b/Makefile.am
> > @@ -231,6 +231,7 @@ shared_sources = src/shared/io.h src/shared/timeout.h \
> >                         src/shared/gap.h src/shared/gap.c \
> >                         src/shared/log.h src/shared/log.c \
> >                         src/shared/bap.h src/shared/bap.c src/shared/ascs.h \
> > +                       src/shared/vcp.c src/shared/vcp.h \
> >                         src/shared/lc3.h src/shared/tty.h
> >
> >  if READLINE
> > diff --git a/src/shared/vcp.c b/src/shared/vcp.c
> > new file mode 100644
> > index 000000000000..944483c60622
> > --- /dev/null
> > +++ b/src/shared/vcp.c
> > @@ -0,0 +1,1104 @@
> > +// 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 "lib/bluetooth.h"
> > +#include "lib/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"
> > +#include "src/log.h"
>
> I went ahead and applied this as is but you need to fix using log.h,
> that won't work when we would be doing unit tests which don't link
> with the daemon, that is why we have set_debug functions for other
> instances, so please fix it.

Thanks for the review. Will send the new updated patch which fixes the same.

>
> > +#define VCP_STEP_SIZE 1
> > +
> > +/* Apllication Error Code */
> > +#define BT_ATT_ERROR_INVALID_CHANGE_COUNTER    0x80
> > +#define BT_ATT_ERROR_OPCODE_NOT_SUPPORTED      0x81
> > +
> > +struct bt_vcp_db {
> > +       struct gatt_db *db;
> > +       struct bt_vcs *vcs;
> > +};
> > +
> > +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_vcs_ab_vol {
> > +       uint8_t change_counter;
> > +       uint8_t vol_set;
> > +} __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 {
> > +       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;
> > +
> > +       struct queue *pending;
> > +
> > +       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;
> > +};
> > +
> > +static struct queue *vcp_db;
> > +static struct queue *vcp_cbs;
> > +static struct queue *sessions;
> > +
> > +static void *iov_pull_mem(struct iovec *iov, size_t len)
> > +{
> > +       void *data = iov->iov_base;
> > +
> > +       if (iov->iov_len < len)
> > +               return NULL;
> > +
> > +       iov->iov_base += len;
> > +       iov->iov_len -= len;
> > +
> > +       return data;
> > +}
> > +
> > +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 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 void vcp_detached(void *data, void *user_data)
> > +{
> > +       struct bt_vcp_cb *cb = data;
> > +       struct bt_vcp *vcp = user_data;
> > +
> > +       cb->detached(vcp, cb->user_data);
> > +}
> > +
> > +void bt_vcp_detach(struct bt_vcp *vcp)
> > +{
> > +       if (!queue_remove(sessions, vcp))
> > +               return;
> > +
> > +       bt_gatt_client_unref(vcp->client);
> > +       vcp->client = NULL;
> > +
> > +       queue_foreach(vcp_cbs, vcp_detached, vcp);
> > +}
> > +
> > +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);
> > +}
> > +
> > +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_disconnected(int err, void *user_data)
> > +{
> > +       struct bt_vcp *vcp = user_data;
> > +
> > +       DBG("vcp %p disconnected err %d", vcp, err);
> > +
> > +       bt_vcp_detach(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;
> > +       }
> > +
> > +       vcp = bt_vcp_new(db, NULL);
> > +       vcp->att = att;
> > +
> > +       bt_att_register_disconnect(att, vcp_disconnected, vcp, NULL);
> > +
> > +       bt_vcp_attach(vcp, NULL);
> > +
> > +       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("");
> > +
> > +       vdb = vcp_get_vdb(vcp);
> > +       if (!vdb) {
> > +               DBG("error: VDB not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       vstate = vdb_get_vstate(vdb);
> > +       if (!vstate) {
> > +               DBG("error: VSTATE not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> > +       if (!change_counter)
> > +               return 0;
> > +
> > +       if (*change_counter != vstate->counter) {
> > +               DBG("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*/
> > +
> > +       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("");
> > +
> > +       vdb = vcp_get_vdb(vcp);
> > +       if (!vdb) {
> > +               DBG("error: VDB not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       vstate = vdb_get_vstate(vdb);
> > +       if (!vstate) {
> > +               DBG("error: VCP database not availalbe!!!!");
> > +               return 0;
> > +       }
> > +
> > +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> > +       if (!change_counter)
> > +               return 0;
> > +
> > +       if (*change_counter != vstate->counter) {
> > +               DBG("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*/
> > +
> > +       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("");
> > +
> > +       vdb = vcp_get_vdb(vcp);
> > +       if (!vdb) {
> > +               DBG("error: VDB not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       vstate = vdb_get_vstate(vdb);
> > +       if (!vstate) {
> > +               DBG("error: VCP database not availalbe!!!!");
> > +               return 0;
> > +       }
> > +
> > +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> > +       if (!change_counter)
> > +               return 0;
> > +
> > +       if (*change_counter != vstate->counter) {
> > +               DBG("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*/
> > +
> > +       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("");
> > +
> > +       vdb = vcp_get_vdb(vcp);
> > +       if (!vdb) {
> > +               DBG("error: VDB not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       vstate = vdb_get_vstate(vdb);
> > +       if (!vstate) {
> > +               DBG("error: VSTATE not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> > +       if (!change_counter)
> > +               return 0;
> > +
> > +       if (*change_counter != vstate->counter) {
> > +               DBG("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*/
> > +
> > +       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("");
> > +
> > +       vdb = vcp_get_vdb(vcp);
> > +       if (!vdb) {
> > +               DBG("error: VDB not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       vstate = vdb_get_vstate(vdb);
> > +       if (!vstate) {
> > +               DBG("error: VSTATE not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       req = iov_pull_mem(iov, sizeof(*req));
> > +       if (!req)
> > +               return 0;
> > +
> > +       if (req->change_counter != vstate->counter) {
> > +               DBG("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*/
> > +
> > +       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("");
> > +
> > +       vdb = vcp_get_vdb(vcp);
> > +       if (!vdb) {
> > +               DBG("error: VDB not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       vstate = vdb_get_vstate(vdb);
> > +       if (!vstate) {
> > +               DBG("error: VSTATE not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> > +       if (!change_counter)
> > +               return 0;
> > +
> > +       if (*change_counter != vstate->counter) {
> > +               DBG("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("");
> > +
> > +       vdb = vcp_get_vdb(vcp);
> > +       if (!vdb) {
> > +               DBG("error: VDB not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       vstate = vdb_get_vstate(vdb);
> > +       if (!vstate) {
> > +               DBG("error: VSTATE not availalbe");
> > +               return 0;
> > +       }
> > +
> > +       change_counter = iov_pull_mem(iov, sizeof(*change_counter));
> > +       if (!change_counter)
> > +               return 0;
> > +
> > +       if (*change_counter != vstate->counter) {
> > +               DBG("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;
> > +}
> > +
> > +#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 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),
> > +       {}
> > +};
> > +
> > +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("");
> > +       if (offset) {
> > +               DBG("invalid offset %d", offset);
> > +               ret = BT_ATT_ERROR_INVALID_OFFSET;
> > +               goto respond;
> > +       }
> > +
> > +       if (len < sizeof(*vcp_op)) {
> > +               DBG("invalid len %ld < %ld sizeof(*param)", len,
> > +                                                       sizeof(*vcp_op));
> > +               ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
> > +               goto respond;
> > +       }
> > +
> > +       vcp_op = iov_pull_mem(&iov, sizeof(*vcp_op));
> > +
> > +       DBG("vcp_op: %x", *vcp_op);
> > +
> > +       for (handler = vcp_handlers; handler && handler->str; handler++) {
> > +               if (handler->op != *vcp_op)
> > +                       continue;
> > +
> > +               if (iov.iov_len < handler->size) {
> > +                       DBG("invalid len %ld < %ld handler->size", len,
> > +                           handler->size);
> > +                       ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
> > +                       goto respond;
> > +               }
> > +
> > +               break;
> > +       }
> > +
> > +       if (handler && handler->str) {
> > +               DBG("%s", handler->str);
> > +
> > +               ret = handler->func(vcs, vcp, &iov);
> > +       } else {
> > +               DBG("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;
> > +
> > +       DBG("");
> > +
> > +       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 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;
> > +
> > +       DBG("vf: %x", vcs->vol_flag);
> > +
> > +       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 struct bt_vcs *vcs_new(struct gatt_db *db)
> > +{
> > +       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, 9);
> > +
> > +       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_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->vcs = vcs_new(db);
> > +       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);
> > +}
> > +
> > +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_vstate_register(uint16_t att_ecode, void *user_data)
> > +{
> > +       DBG("");
> > +       if (att_ecode)
> > +               DBG("VCS register failed: 0x%04x", att_ecode);
> > +}
> > +
> > +static void vcp_vflag_register(uint16_t att_ecode, void *user_data)
> > +{
> > +       DBG("");
> > +       if (att_ecode)
> > +               DBG("VCS register failed: 0x%04x", att_ecode);
> > +}
> > +
> > +static void vcp_vstate_notify(uint16_t value_handle, const uint8_t *value,
> > +                               uint16_t length, void *user_data)
> > +{
> > +       struct vol_state vstate;
> > +
> > +       memcpy(&vstate, value, sizeof(struct vol_state));
> > +
> > +       DBG("Vol Settings 0x%x", vstate.vol_set);
> > +       DBG("Mute Status 0x%x", vstate.mute);
> > +       DBG("Vol Counter 0x%x", vstate.counter);
> > +}
> > +
> > +static void vcp_vflag_notify(uint16_t value_handle, const uint8_t *value,
> > +                               uint16_t length, void *user_data)
> > +{
> > +       uint8_t vflag;
> > +
> > +       memcpy(&vflag, value, sizeof(vflag));
> > +
> > +       DBG("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)
> > +{
> > +       uint8_t *vol_flag;
> > +       struct iovec iov = {
> > +               .iov_base = (void *) value,
> > +               .iov_len = length,
> > +       };
> > +
> > +       if (!success) {
> > +               DBG("Unable to read VCP Vol State: error 0x%02x", att_ecode);
> > +               return;
> > +       }
> > +
> > +       vol_flag = iov_pull_mem(&iov, sizeof(*vol_flag));
> > +       if (!vol_flag) {
> > +               DBG("Unable to get Vol State");
> > +               return;
> > +       }
> > +
> > +       DBG("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("Unable to read VCP Vol State: error 0x%02x", att_ecode);
> > +               return;
> > +       }
> > +
> > +       vs = iov_pull_mem(&iov, sizeof(*vs));
> > +       if (!vs) {
> > +               DBG("Unable to get Vol State");
> > +               return;
> > +       }
> > +
> > +       DBG("Vol Set:%x", vs->vol_set);
> > +       DBG("Vol Mute:%x", vs->mute);
> > +       DBG("Vol Counter:%x", vs->counter);
> > +
> > +}
> > +
> > +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("Unable to send Read request");
> > +               free(pending);
> > +               return;
> > +       }
> > +
> > +       queue_push_tail(vcp->pending, pending);
> > +}
> > +
> > +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;
> > +
> > +       DBG("");
> > +       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("VCS Volume state found: handle 0x%04x", value_handle);
> > +
> > +               vcs = vcp_get_vcs(vcp);
> > +               if (!vcs || vcs->vs)
> > +                       return;
> > +
> > +               vcs->vs = attr;
> > +
> > +               vcp_read_value(vcp, value_handle, read_vol_state, vcp);
> > +               vcp->vstate_id = bt_gatt_client_register_notify(vcp->client,
> > +                                               value_handle,
> > +                                               vcp_vstate_register,
> > +                                               vcp_vstate_notify, vcp, NULL);
> > +
> > +               return;
> > +       }
> > +
> > +       if (!bt_uuid_cmp(&uuid, &uuid_cp)) {
> > +               DBG("VCS Volume CP found: handle 0x%04x", value_handle);
> > +
> > +               vcs = vcp_get_vcs(vcp);
> > +               if (!vcs || vcs->vol_cp)
> > +                       return;
> > +
> > +               vcs->vol_cp = attr;
> > +
> > +               return;
> > +       }
> > +
> > +       if (!bt_uuid_cmp(&uuid, &uuid_vflag)) {
> > +               DBG("VCS Vol Flaf found: handle 0x%04x", value_handle);
> > +
> > +               vcs = vcp_get_vcs(vcp);
> > +               if (!vcs || vcs->vf)
> > +                       return;
> > +
> > +               vcs->vf = attr;
> > +
> > +               vcp_read_value(vcp, value_handle, read_vol_flag, vcp);
> > +               vcp->vflag_id = bt_gatt_client_register_notify(vcp->client,
> > +                                               value_handle,
> > +                                               vcp_vflag_register,
> > +                                               vcp_vflag_notify, vcp, 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);
> > +
> > +       DBG("");
> > +       vcs->service = attr;
> > +
> > +       gatt_db_service_set_claimed(attr, true);
> > +
> > +       gatt_db_service_foreach_char(attr, foreach_vcs_char, vcp);
> > +}
> > +
> > +bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client)
> > +{
> > +       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->ldb->db, &uuid, foreach_vcs_service, vcp);
> > +
> > +       return true;
> > +}
> > +
> > diff --git a/src/shared/vcp.h b/src/shared/vcp.h
> > new file mode 100644
> > index 000000000000..456ad8041162
> > --- /dev/null
> > +++ b/src/shared/vcp.h
> > @@ -0,0 +1,58 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + *
> > + *  BlueZ - Bluetooth protocol stack for Linux
> > + *
> > + *  Copyright (C) 2020  Intel Corporation. All rights reserved.
> > + *
> > + */
> > +
> > +#include <stdbool.h>
> > +#include <inttypes.h>
> > +
> > +#include "src/shared/io.h"
> > +
> > +#ifndef __packed
> > +#define __packed __attribute__((packed))
> > +#endif
> > +
> > +#define BT_VCP_RENDERER                        0x01
> > +#define        BT_VCP_CONTROLLER               0x02
> > +
> > +#define BT_VCP_RELATIVE_VOL_DOWN       0x00
> > +#define BT_VCP_RELATIVE_VOL_UP         0x01
> > +#define BT_VCP_UNMUTE_RELATIVE_VOL_DOWN        0x02
> > +#define BT_VCP_UNMUTE_RELATIVE_VOL_UP  0x03
> > +#define BT_VCP_SET_ABOSULTE_VOL                0x04
> > +#define BT_VCP_UNMUTE                  0x05
> > +#define BT_VCP_MUTE                    0x06
> > +
> > +#ifndef MAX
> > +#define MAX(a, b) ((a) > (b) ? (a) : (b))
> > +#endif
> > +
> > +#ifndef MIN
> > +#define MIN(a, b) ((a) < (b) ? (a) : (b))
> > +#endif
> > +
> > +struct bt_vcp;
> > +
> > +typedef void (*bt_vcp_func_t)(struct bt_vcp *vcp, void *user_data);
> > +
> > +struct bt_vcp *bt_vcp_ref(struct bt_vcp *vcp);
> > +void bt_vcp_unref(struct bt_vcp *vcp);
> > +
> > +void bt_vcp_add_db(struct gatt_db *db);
> > +
> > +bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client);
> > +void bt_vcp_detach(struct bt_vcp *vcp);
> > +
> > +struct bt_att *bt_vcp_get_att(struct bt_vcp *vcp);
> > +
> > +bool bt_vcp_set_user_data(struct bt_vcp *vcp, void *user_data);
> > +
> > +/* Session related function */
> > +unsigned int bt_vcp_register(bt_vcp_func_t added, bt_vcp_func_t removed,
> > +                                                       void *user_data);
> > +bool bt_vcp_unregister(unsigned int id);
> > +struct bt_vcp *bt_vcp_new(struct gatt_db *ldb, struct gatt_db *rdb);
> > --
> > 2.25.1
> >
>
>
> --
> Luiz Augusto von Dentz

Sathish N

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2022-09-20 10:27 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-09-19  8:07 [PATCH BlueZ v3 1/3] shared/vcp: Add initial code for handling VCP Sathish Narasimman
2022-09-19  8:07 ` [PATCH BlueZ v3 2/3] profiles: Add initial code for vcp plugin Sathish Narasimman
2022-09-19  8:07 ` [PATCH BlueZ v3 3/3] monitor/att: Add decoding support for Volume Control Serice Sathish Narasimman
2022-09-19  9:36 ` [BlueZ,v3,1/3] shared/vcp: Add initial code for handling VCP bluez.test.bot
2022-09-19 22:35 ` [PATCH BlueZ v3 1/3] " Luiz Augusto von Dentz
2022-09-20 10:26   ` Sathish Narasimman

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).