@@ -39,6 +39,9 @@
#define BT_ATT_ERROR_INVALID_CHANGE_COUNTER 0x80
#define BT_ATT_ERROR_OPCODE_NOT_SUPPORTED 0x81
#define BT_ATT_ERROR_VALUE_OUT_OF_RANGE 0x82
+#define BT_ATT_AICS_ERROR_VALUE_OUT_OF_RANGE 0x83
+#define BT_ATT_AICS_ERROR_MUTE_DISABLED 0x82
+#define BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED 0x84
#define BT_VCP_NA BIT(0)
#define BT_VCP_FRONT_LEFT BIT(1)
@@ -70,10 +73,52 @@
#define BT_VCP_LEFT_SURROUND BIT(27)
#define BT_VCP_RIGHT_SURROUND BIT(28)
+#define VCS_TOTAL_NUM_HANDLES 11
+#define AICS_TOTAL_NUM_HANDLES 16
+
+/* AICS Audio Input Type Values */
+#define AICS_AUD_IP_TYPE_UNSPECIFIED 0x00
+#define AICS_AUD_IP_TYPE_BLUETOOTH 0x01
+#define AICS_AUD_IP_TYPE_MICROPHONE 0x02
+#define AICS_AUD_IP_TYPE_ANALOG 0x03
+#define AICS_AUD_IP_TYPE_DIGITAL 0x04
+#define AICS_AUD_IP_TYPE_RADIO 0x05
+#define AICS_AUD_IP_TYPE_STREAMING 0x06
+#define AICS_AUD_IP_TYPE_AMBIENT 0x07
+
+/* AICS Audio Input Status Values */
+#define AICS_AUD_IP_STATUS_INACTIVE 0x00
+#define AICS_AUD_IP_STATUS_ACTIVE 0x01
+
+/* AICS Audio Input Control Point Opcodes */
+#define BT_AICS_SET_GAIN_SETTING 0x01
+#define BT_AICS_UNMUTE 0x02
+#define BT_AICS_MUTE 0x03
+#define BT_AICS_SET_MANUAL_GAIN_MODE 0x04
+#define BT_AICS_SET_AUTO_GAIN_MODE 0x05
+
+/* AICS Gain Mode Field Value */
+#define AICS_GAIN_MODE_MANUAL_ONLY 0x00
+#define AICS_GAIN_MODE_AUTO_ONLY 0x01
+#define AICS_GAIN_MODE_MANUAL 0x02
+#define AICS_GAIN_MODE_AUTO 0x03
+
+/* AICS Mute Field Values */
+#define AICS_NOT_MUTED 0x00
+#define AICS_MUTED 0x01
+#define AICS_DISABLED 0x02
+
+#define AICS_GAIN_SETTING_UNITS 1
+#define AICS_GAIN_SETTING_MAX_VALUE 127
+#define AICS_GAIN_SETTING_MIN_VALUE -128
+
+#define AICS_GAIN_SETTING_DEFAULT_VALUE 88
+
struct bt_vcp_db {
struct gatt_db *db;
struct bt_vcs *vcs;
struct bt_vocs *vocs;
+ struct bt_aics *aics;
};
typedef void (*vcp_func_t)(struct bt_vcp *vcp, bool success, uint8_t att_ecode,
@@ -138,6 +183,10 @@ struct bt_vcp {
unsigned int audio_loc_id;
unsigned int ao_dec_id;
+ unsigned int aics_ip_state_id;
+ unsigned int aics_ip_status_id;
+ unsigned int aics_ip_descr_id;
+
struct queue *notify;
struct queue *pending;
@@ -190,6 +239,43 @@ struct bt_vocs {
struct gatt_db_attribute *voaodec_ccc;
};
+struct aud_ip_st {
+ int8_t gain_setting;
+ uint8_t mute;
+ uint8_t gain_mode;
+ uint8_t chg_counter;
+} __packed;
+
+struct gain_setting_prop {
+ uint8_t gain_setting_units;
+ int8_t gain_setting_min;
+ int8_t gain_setting_max;
+} __packed;
+
+struct bt_aics_set_gain_setting {
+ uint8_t change_counter;
+ int8_t gain_setting;
+} __packed;
+
+struct bt_aics {
+ struct bt_vcp_db *vdb;
+ struct aud_ip_st *aud_ipst;
+ struct gain_setting_prop *gain_settingprop;
+ uint8_t aud_input_type;
+ uint8_t aud_input_status;
+ char *aud_input_descr;
+ struct gatt_db_attribute *service;
+ struct gatt_db_attribute *aud_ip_state;
+ struct gatt_db_attribute *aud_ip_state_ccc;
+ struct gatt_db_attribute *gain_stting_prop;
+ struct gatt_db_attribute *aud_ip_type;
+ struct gatt_db_attribute *aud_ip_status;
+ struct gatt_db_attribute *aud_ip_status_ccc;
+ struct gatt_db_attribute *aud_ip_cp;
+ struct gatt_db_attribute *aud_ip_dscrptn;
+ struct gatt_db_attribute *aud_ip_dscrptn_ccc;
+};
+
static struct queue *vcp_db;
static struct queue *vcp_cbs;
static struct queue *sessions;
@@ -268,6 +354,20 @@ static struct bt_vocs *vcp_get_vocs(struct bt_vcp *vcp)
return vcp->rdb->vocs;
}
+static struct bt_aics *vcp_get_aics(struct bt_vcp *vcp)
+{
+ if (!vcp)
+ return NULL;
+
+ if (vcp->rdb->aics)
+ return vcp->rdb->aics;
+
+ vcp->rdb->aics = new0(struct bt_aics, 1);
+ vcp->rdb->aics->vdb = vcp->rdb;
+
+ return vcp->rdb->aics;
+}
+
static void vcp_detached(void *data, void *user_data)
{
struct bt_vcp_cb *cb = data;
@@ -298,6 +398,7 @@ static void vcp_db_free(void *data)
free(vdb->vcs);
free(vdb->vocs);
+ free(vdb->aics);
free(vdb);
}
@@ -982,6 +1083,488 @@ static void vocs_voaodec_read(struct gatt_db_attribute *attrib,
iov.iov_len);
}
+static void aics_input_state_read(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_aics *aics = user_data;
+ struct iovec iov;
+
+ iov.iov_base = aics->aud_ipst;
+ iov.iov_len = sizeof(*aics->aud_ipst);
+
+ gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+ iov.iov_len);
+}
+
+static void aics_gain_setting_prop_read(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_aics *aics = user_data;
+ struct iovec iov;
+
+ iov.iov_base = aics->gain_settingprop;
+ iov.iov_len = sizeof(*aics->gain_settingprop);
+
+ gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+ iov.iov_len);
+}
+
+static void aics_audio_input_type_read(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_aics *aics = user_data;
+ struct iovec iov;
+
+ iov.iov_base = &aics->aud_input_type;
+ iov.iov_len = sizeof(aics->aud_input_type);
+
+ gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+ iov.iov_len);
+}
+
+static void aics_input_status_read(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_aics *aics = user_data;
+ struct iovec iov;
+
+ iov.iov_base = &aics->aud_input_status;
+ iov.iov_len = sizeof(aics->aud_input_status);
+
+ gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+ iov.iov_len);
+}
+
+static struct aud_ip_st *vdb_get_audipst(struct bt_vcp_db *vdb)
+{
+ if (!vdb->aics)
+ return NULL;
+
+ if (vdb->aics->aud_ipst)
+ return vdb->aics->aud_ipst;
+
+ return NULL;
+}
+
+static struct gain_setting_prop *vdb_get_gainsettingprop(
+ struct bt_vcp_db *vdb)
+{
+ if (!vdb->aics)
+ return NULL;
+
+ if (vdb->aics->gain_settingprop)
+ return vdb->aics->gain_settingprop;
+
+ return NULL;
+}
+
+static uint8_t aics_set_gain_setting(struct bt_aics *aics,
+ struct bt_vcp *vcp, struct iovec *iov)
+{
+ struct bt_vcp_db *vdb;
+ struct aud_ip_st *audipst;
+ struct bt_aics_set_gain_setting *req;
+ struct gain_setting_prop *gainsettngprop;
+ uint8_t ret = 1;
+
+ vdb = vcp_get_vdb(vcp);
+ if (!vdb) {
+ DBG(vcp, "error: VDB not available");
+ ret = 0;
+ goto respond;
+ }
+
+ audipst = vdb_get_audipst(vdb);
+ if (!audipst) {
+ DBG(vcp, "error: Audio Input State value is not available");
+ ret = 0;
+ goto respond;
+
+ }
+
+ req = iov_pull_mem(iov, sizeof(*req));
+ if (!req) {
+ ret = 0;
+ goto respond;
+
+ }
+
+ if (req->change_counter != audipst->chg_counter) {
+ DBG(vcp, "Change Counter Mismatch Audio Input State!");
+ ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
+ goto respond;
+ }
+
+ if (audipst->gain_mode != AICS_GAIN_MODE_MANUAL_ONLY &&
+ audipst->gain_mode != AICS_GAIN_MODE_MANUAL) {
+ DBG(vcp, "Gain Mode is not Manual only or Manual");
+ ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
+ goto respond;
+ }
+
+ gainsettngprop = vdb_get_gainsettingprop(vdb);
+ if (req->gain_setting > gainsettngprop->gain_setting_max ||
+ req->gain_setting < gainsettngprop->gain_setting_min) {
+ DBG(vcp, "error: Value Out of Range");
+ ret = BT_ATT_AICS_ERROR_VALUE_OUT_OF_RANGE;
+ goto respond;
+ }
+
+ audipst->gain_setting = req->gain_setting;
+ /*Increment Change Counter*/
+ audipst->chg_counter = -~audipst->chg_counter;
+ gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst,
+ sizeof(struct aud_ip_st),
+ bt_vcp_get_att(vcp));
+ ret = 0;
+
+respond:
+ return ret;
+}
+
+static uint8_t aics_unmute(struct bt_aics *aics, struct bt_vcp *vcp,
+ struct iovec *iov)
+{
+ struct bt_vcp_db *vdb;
+ struct aud_ip_st *audipst;
+ uint8_t *change_counter;
+ uint8_t ret = 1;
+
+ vdb = vcp_get_vdb(vcp);
+ if (!vdb) {
+ DBG(vcp, "error: VDB not available");
+ ret = 0;
+ goto respond;
+
+ }
+
+ audipst = vdb_get_audipst(vdb);
+ if (!audipst) {
+ DBG(vcp, "error: Audio Input State value is not available");
+ ret = 0;
+ goto respond;
+
+ }
+ change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+ if (!change_counter) {
+ ret = 0;
+ goto respond;
+
+ }
+
+ if (*change_counter != audipst->chg_counter) {
+ DBG(vcp, "Change Counter Mismatch Audio Input State!");
+ ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
+ goto respond;
+ }
+
+ if (audipst->mute == AICS_DISABLED) {
+ DBG(vcp, "Mute state is Disabled!");
+ ret = BT_ATT_AICS_ERROR_MUTE_DISABLED;
+ goto respond;
+ }
+
+ audipst->mute = AICS_NOT_MUTED;
+ /*Increment Change Counter*/
+ audipst->chg_counter = -~audipst->chg_counter;
+ gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst,
+ sizeof(struct aud_ip_st),
+ bt_vcp_get_att(vcp));
+ ret = 0;
+
+respond:
+ return ret;
+}
+
+static uint8_t aics_mute(struct bt_aics *aics, struct bt_vcp *vcp,
+ struct iovec *iov)
+{
+ struct bt_vcp_db *vdb;
+ struct aud_ip_st *audipst;
+ uint8_t *change_counter;
+ uint8_t ret = 1;
+
+ vdb = vcp_get_vdb(vcp);
+ if (!vdb) {
+ DBG(vcp, "error: VDB not available");
+ ret = 0;
+ goto respond;
+ }
+
+ audipst = vdb_get_audipst(vdb);
+ if (!audipst) {
+ DBG(vcp, "error: Audio Input State value is not available");
+ ret = 0;
+ goto respond;
+ }
+ change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+ if (!change_counter) {
+ ret = 0;
+ goto respond;
+ }
+
+ if (*change_counter != audipst->chg_counter) {
+ DBG(vcp, "Change Counter Mismatch Audio Input State!");
+ ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
+ goto respond;
+ }
+
+ if (audipst->mute == AICS_DISABLED) {
+ DBG(vcp, "Mute state is Disabled!");
+ ret = BT_ATT_AICS_ERROR_MUTE_DISABLED;
+ goto respond;
+ }
+
+ audipst->mute = AICS_MUTED;
+ /*Increment Change Counter*/
+ audipst->chg_counter = -~audipst->chg_counter;
+ gatt_db_attribute_notify(vdb->aics->aud_ip_state, (void *)audipst,
+ sizeof(struct aud_ip_st),
+ bt_vcp_get_att(vcp));
+ ret = 0;
+
+respond:
+ return ret;
+}
+
+static uint8_t aics_set_manual_gain_mode(struct bt_aics *aics,
+ struct bt_vcp *vcp, struct iovec *iov)
+{
+ struct bt_vcp_db *vdb;
+ struct aud_ip_st *audipst;
+ uint8_t *change_counter;
+ uint8_t ret = 1;
+
+ vdb = vcp_get_vdb(vcp);
+ if (!vdb) {
+ DBG(vcp, "error: VDB not available");
+ ret = 0;
+ goto respond;
+ }
+
+ audipst = vdb_get_audipst(vdb);
+ if (!audipst) {
+ DBG(vcp, "error: Audio Input State value is not available");
+ ret = 0;
+ goto respond;
+ }
+
+ change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+ if (!change_counter) {
+ ret = 0;
+ goto respond;
+ }
+
+ if (*change_counter != audipst->chg_counter) {
+ DBG(vcp, "Change Counter Mismatch Audio Input State!");
+ ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
+ goto respond;
+ }
+
+ if (audipst->gain_mode == AICS_GAIN_MODE_AUTO_ONLY ||
+ audipst->gain_mode == AICS_GAIN_MODE_MANUAL_ONLY) {
+ DBG(vcp, "error!! gain mode is Automatic only or Manual only");
+ ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
+ goto respond;
+ }
+
+ if (audipst->gain_mode == AICS_GAIN_MODE_AUTO) {
+ audipst->gain_mode = AICS_GAIN_MODE_MANUAL;
+ /*Increment Change Counter*/
+ audipst->chg_counter = -~audipst->chg_counter;
+ gatt_db_attribute_notify(vdb->aics->aud_ip_state,
+ (void *)audipst,
+ sizeof(struct aud_ip_st),
+ bt_vcp_get_att(vcp));
+ ret = 0;
+ } else {
+ DBG(vcp,
+ "error!! Gain mode field value not Automatic");
+ ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
+ }
+
+respond:
+ return ret;
+}
+
+static uint8_t aics_set_auto_gain_mode(struct bt_aics *aics, struct bt_vcp *vcp,
+ struct iovec *iov)
+{
+ struct bt_vcp_db *vdb;
+ struct aud_ip_st *audipst;
+ uint8_t *change_counter;
+ uint8_t ret = 1;
+
+ vdb = vcp_get_vdb(vcp);
+ if (!vdb) {
+ DBG(vcp, "error: VDB not available");
+ ret = 0;
+ goto respond;
+ }
+
+ audipst = vdb_get_audipst(vdb);
+ if (!audipst) {
+ DBG(vcp, "error: Audio Input State value is not available");
+ ret = 0;
+ goto respond;
+ }
+ change_counter = iov_pull_mem(iov, sizeof(*change_counter));
+ if (!change_counter) {
+ ret = 0;
+ goto respond;
+ }
+
+ if (*change_counter != audipst->chg_counter) {
+ DBG(vcp, "Change Counter Mismatch Audio Input State!");
+ ret = BT_ATT_ERROR_INVALID_CHANGE_COUNTER;
+ goto respond;
+ }
+
+ if (audipst->gain_mode == AICS_GAIN_MODE_AUTO_ONLY ||
+ audipst->gain_mode == AICS_GAIN_MODE_MANUAL_ONLY) {
+ DBG(vcp, "error!! gain mode is Automatic only or Manual only");
+ ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
+ goto respond;
+ }
+
+ if (audipst->gain_mode == AICS_GAIN_MODE_MANUAL) {
+ audipst->gain_mode = AICS_GAIN_MODE_AUTO;
+ /*Increment Change Counter*/
+ audipst->chg_counter = -~audipst->chg_counter;
+ gatt_db_attribute_notify(vdb->aics->aud_ip_state,
+ (void *)audipst,
+ sizeof(struct aud_ip_st), bt_vcp_get_att(vcp));
+ ret = 0;
+ } else {
+ DBG(vcp, "error!! Gain mode field value is not Manual");
+ ret = BT_ATT_AICS_ERROR_GAIN_MODE_CHANGE_NOT_ALLOWED;
+ }
+
+respond:
+ return ret;
+}
+
+#define AICS_OP(_str, _op, _size, _func) \
+ { \
+ .str = _str, \
+ .op = _op, \
+ .size = _size, \
+ .func = _func, \
+ }
+
+struct aics_op_handler {
+ const char *str;
+ uint8_t op;
+ size_t size;
+ uint8_t (*func)(struct bt_aics *aics, struct bt_vcp *vcp,
+ struct iovec *iov);
+} aics_handlers[] = {
+ AICS_OP("Set Gain Setting", BT_AICS_SET_GAIN_SETTING,
+ sizeof(struct bt_aics_set_gain_setting),
+ aics_set_gain_setting),
+ AICS_OP("Unmute", BT_AICS_UNMUTE,
+ sizeof(uint8_t), aics_unmute),
+ AICS_OP("Mute", BT_AICS_MUTE,
+ sizeof(uint8_t), aics_mute),
+ AICS_OP("Set Manual Gain Mode", BT_AICS_SET_MANUAL_GAIN_MODE,
+ sizeof(uint8_t), aics_set_manual_gain_mode),
+ AICS_OP("Set Automatic Gain Mode", BT_AICS_SET_AUTO_GAIN_MODE,
+ sizeof(uint8_t), aics_set_auto_gain_mode),
+ {}
+};
+
+static void aics_ip_cp_write(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_aics *aics = user_data;
+ struct bt_vcp *vcp = vcp_get_session(att, aics->vdb->db);
+ struct iovec iov = {
+ .iov_base = (void *) value,
+ .iov_len = len,
+ };
+ uint8_t *aics_op;
+ struct aics_op_handler *handler;
+ uint8_t ret = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED;
+
+ DBG(vcp, "AICS Control Point Write");
+
+ if (offset) {
+ DBG(vcp, "invalid offset %d", offset);
+ ret = BT_ATT_ERROR_INVALID_OFFSET;
+ goto respond;
+ }
+
+ if (len < sizeof(*aics_op)) {
+ DBG(vcp, "invalid len %ld < %ld sizeof(*param)", len,
+ sizeof(*aics_op));
+ ret = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN;
+ goto respond;
+ }
+
+ aics_op = iov_pull_mem(&iov, sizeof(*aics_op));
+
+ for (handler = aics_handlers; handler && handler->str; handler++) {
+ if (handler->op != *aics_op)
+ continue;
+
+ if (iov.iov_len < handler->size) {
+ DBG(vcp, "invalid len %ld < %ld handler->size", len,
+ handler->size);
+ ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
+ goto respond;
+ }
+
+ break;
+ }
+
+ if (handler && handler->str) {
+ DBG(vcp, "%s", handler->str);
+
+ ret = handler->func(aics, vcp, &iov);
+ } else {
+ DBG(vcp, "Unknown opcode 0x%02x", *aics_op);
+ ret = BT_ATT_ERROR_OPCODE_NOT_SUPPORTED;
+ }
+
+respond:
+ gatt_db_attribute_write_result(attrib, id, ret);
+}
+
+static void aics_input_descr_read(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ struct bt_aics *aics = user_data;
+ struct iovec iov;
+
+ iov.iov_base = aics->aud_input_descr;
+ iov.iov_len = strlen(aics->aud_input_descr);
+
+ gatt_db_attribute_read_result(attrib, id, 0, iov.iov_base,
+ iov.iov_len);
+}
+
+static void aics_input_descr_write(struct gatt_db_attribute *attrib,
+ unsigned int id, uint16_t offset,
+ const uint8_t *value, size_t len,
+ uint8_t opcode, struct bt_att *att,
+ void *user_data)
+{
+ /* TODO : AICS optional feature */
+}
+
static struct bt_vcs *vcs_new(struct gatt_db *db, struct bt_vcp_db *vdb)
{
struct bt_vcs *vcs;
@@ -1000,9 +1583,12 @@ static struct bt_vcs *vcs_new(struct gatt_db *db, struct bt_vcp_db *vdb)
/* Populate DB with VCS attributes */
bt_uuid16_create(&uuid, VCS_UUID);
- vcs->service = gatt_db_add_service(db, &uuid, true, 10);
+ vcs->service = gatt_db_add_service(db, &uuid, true,
+ VCS_TOTAL_NUM_HANDLES);
gatt_db_service_add_included(vcs->service, vdb->vocs->service);
gatt_db_service_set_active(vdb->vocs->service, true);
+ gatt_db_service_add_included(vcs->service, vdb->aics->service);
+ gatt_db_service_set_active(vdb->aics->service, true);
bt_uuid16_create(&uuid, VOL_STATE_CHRC_UUID);
vcs->vs = gatt_db_service_add_characteristic(vcs->service,
@@ -1111,6 +1697,108 @@ static struct bt_vocs *vocs_new(struct gatt_db *db)
return vocs;
}
+static struct bt_aics *aics_new(struct gatt_db *db)
+{
+ struct bt_aics *aics;
+ struct aud_ip_st *aics_aud_ip_st;
+ struct gain_setting_prop *aics_gain_settng_prop;
+ char *ip_descr;
+ char ip_descr_str[] = "Blueooth";
+ bt_uuid_t uuid;
+
+ if (!db)
+ return NULL;
+
+ aics = new0(struct bt_aics, 1);
+
+ aics_aud_ip_st = new0(struct aud_ip_st, 1);
+ aics_gain_settng_prop = new0(struct gain_setting_prop, 1);
+ ip_descr = malloc(256);
+ memset(ip_descr, 0, 256);
+
+ aics_aud_ip_st->mute = AICS_NOT_MUTED;
+ aics_aud_ip_st->gain_mode = AICS_GAIN_MODE_MANUAL;
+ aics_aud_ip_st->gain_setting = AICS_GAIN_SETTING_DEFAULT_VALUE;
+ aics->aud_ipst = aics_aud_ip_st;
+ aics_gain_settng_prop->gain_setting_units = AICS_GAIN_SETTING_UNITS;
+ aics_gain_settng_prop->gain_setting_max = AICS_GAIN_SETTING_MAX_VALUE;
+ aics_gain_settng_prop->gain_setting_min = AICS_GAIN_SETTING_MIN_VALUE;
+ aics->gain_settingprop = aics_gain_settng_prop;
+ aics->aud_input_type = AICS_AUD_IP_TYPE_BLUETOOTH;
+ aics->aud_input_status = AICS_AUD_IP_STATUS_ACTIVE;
+ memcpy(ip_descr, ip_descr_str, strlen(ip_descr_str));
+ aics->aud_input_descr = ip_descr;
+
+ /* Populate DB with AICS attributes */
+ bt_uuid16_create(&uuid, AUDIO_INPUT_CS_UUID);
+ aics->service = gatt_db_add_service(db, &uuid, false,
+ AICS_TOTAL_NUM_HANDLES);
+
+ bt_uuid16_create(&uuid, AICS_INPUT_STATE_CHAR_UUID);
+ aics->aud_ip_state = gatt_db_service_add_characteristic(aics->service,
+ &uuid,
+ BT_ATT_PERM_READ,
+ BT_GATT_CHRC_PROP_READ |
+ BT_GATT_CHRC_PROP_NOTIFY,
+ aics_input_state_read,
+ NULL,
+ aics);
+ aics->aud_ip_state_ccc = gatt_db_service_add_ccc(aics->service,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+ bt_uuid16_create(&uuid, AICS_GAIN_SETTING_PROP_CHAR_UUID);
+ aics->gain_stting_prop = gatt_db_service_add_characteristic(
+ aics->service,
+ &uuid,
+ BT_ATT_PERM_READ,
+ BT_GATT_CHRC_PROP_READ,
+ aics_gain_setting_prop_read, NULL,
+ aics);
+
+ bt_uuid16_create(&uuid, AICS_AUDIO_INPUT_TYPE_CHAR_UUID);
+ aics->aud_ip_type = gatt_db_service_add_characteristic(aics->service,
+ &uuid,
+ BT_ATT_PERM_READ,
+ BT_GATT_CHRC_PROP_READ,
+ aics_audio_input_type_read, NULL,
+ aics);
+
+ bt_uuid16_create(&uuid, AICS_INPUT_STATUS_CHAR_UUID);
+ aics->aud_ip_status = gatt_db_service_add_characteristic(aics->service,
+ &uuid,
+ BT_ATT_PERM_READ,
+ BT_GATT_CHRC_PROP_READ |
+ BT_GATT_CHRC_PROP_NOTIFY,
+ aics_input_status_read, NULL,
+ aics);
+ aics->aud_ip_status_ccc = gatt_db_service_add_ccc(aics->service,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+ bt_uuid16_create(&uuid, AICS_AUDIO_INPUT_CP_CHRC_UUID);
+ aics->aud_ip_cp = gatt_db_service_add_characteristic(aics->service,
+ &uuid,
+ BT_ATT_PERM_WRITE,
+ BT_GATT_CHRC_PROP_WRITE,
+ NULL, aics_ip_cp_write,
+ aics);
+
+ bt_uuid16_create(&uuid, AICS_INPUT_DESCR_CHAR_UUID);
+ aics->aud_ip_dscrptn = gatt_db_service_add_characteristic(aics->service,
+ &uuid,
+ BT_ATT_PERM_READ |
+ BT_ATT_PERM_WRITE,
+ BT_GATT_CHRC_PROP_READ |
+ BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP |
+ BT_GATT_CHRC_PROP_NOTIFY,
+ aics_input_descr_read,
+ aics_input_descr_write,
+ aics);
+ aics->aud_ip_dscrptn_ccc = gatt_db_service_add_ccc(aics->service,
+ BT_ATT_PERM_READ | BT_ATT_PERM_WRITE);
+
+ return aics;
+}
+
static struct bt_vcp_db *vcp_db_new(struct gatt_db *db)
{
struct bt_vcp_db *vdb;
@@ -1126,6 +1814,10 @@ static struct bt_vcp_db *vcp_db_new(struct gatt_db *db)
vdb->vocs = vocs_new(db);
vdb->vocs->vdb = vdb;
+
+ vdb->aics = aics_new(db);
+ vdb->aics->vdb = vdb;
+
vdb->vcs = vcs_new(db, vdb);
vdb->vcs->vdb = vdb;
@@ -1685,6 +2377,307 @@ static void foreach_vocs_char(struct gatt_db_attribute *attr, void *user_data)
}
+static void read_aics_audio_ip_state(struct bt_vcp *vcp, bool success,
+ uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct aud_ip_st *ip_st;
+ struct iovec iov = {
+ .iov_base = (void *) value,
+ .iov_len = length,
+ };
+
+ if (!success) {
+ DBG(vcp, "Unable to read Audio Input State: error 0x%02x",
+ att_ecode);
+ return;
+ }
+
+ ip_st = iov_pull_mem(&iov, sizeof(*ip_st));
+ if (!ip_st) {
+ DBG(vcp, "Unable to get Audio Input State");
+ return;
+ }
+
+ DBG(vcp, "Audio Input State, Gain Setting:%d", ip_st->gain_setting);
+ DBG(vcp, "Audio Input State, Mute:%x", ip_st->mute);
+ DBG(vcp, "Audio Input State, Gain Mode:%x", ip_st->gain_mode);
+ DBG(vcp, "Audio Input State, Change Counter:%x", ip_st->chg_counter);
+}
+
+static void aics_ip_state_notify(struct bt_vcp *vcp, uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct aud_ip_st ip_st;
+
+ memcpy(&ip_st, value, sizeof(struct aud_ip_st));
+
+ DBG(vcp, "Audio Input State, Gain Setting:%d", ip_st.gain_setting);
+ DBG(vcp, "Audio Input State, Mute:%x", ip_st.mute);
+ DBG(vcp, "Audio Input State, Gain Mode:%x", ip_st.gain_mode);
+ DBG(vcp, "Audio Input State, Change Counter:%x", ip_st.chg_counter);
+}
+
+static void read_aics_gain_setting_prop(struct bt_vcp *vcp, bool success,
+ uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ struct gain_setting_prop *aics_gain_setting_prop;
+ struct iovec iov = {
+ .iov_base = (void *) value,
+ .iov_len = length,
+ };
+
+ if (!value) {
+ DBG(vcp, "Unable to get Gain Setting Properties Char");
+ return;
+ }
+
+ if (!success) {
+ DBG(vcp,
+ "Unable to read Gain Setting Properties Char: 0x%02x",
+ att_ecode);
+ return;
+ }
+
+ aics_gain_setting_prop = iov_pull_mem(&iov,
+ sizeof(*aics_gain_setting_prop));
+ if (!aics_gain_setting_prop) {
+ DBG(vcp, "Unable to get Gain Setting Properties Char");
+ return;
+ }
+
+ DBG(vcp, "Gain Setting Properties, Units: %x",
+ aics_gain_setting_prop->gain_setting_units);
+ DBG(vcp, "Gain Setting Properties, Min Value: %d",
+ aics_gain_setting_prop->gain_setting_min);
+ DBG(vcp, "Gain Setting Properties, Max Value: %d",
+ aics_gain_setting_prop->gain_setting_max);
+}
+
+static void read_aics_aud_ip_type(struct bt_vcp *vcp, bool success,
+ uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ uint8_t ip_type;
+
+ if (!success) {
+ DBG(vcp,
+ "Unable to read Audio Input Type Char: error 0x%02x",
+ att_ecode);
+ return;
+ }
+
+ memcpy(&ip_type, value, length);
+
+ DBG(vcp, "Audio Input Type : %x", ip_type);
+}
+
+static void read_aics_audio_ip_status(struct bt_vcp *vcp, bool success,
+ uint8_t att_ecode,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ uint8_t ip_status;
+
+ if (!success) {
+ DBG(vcp,
+ "Unable to read Audio Input Status Char: 0x%02x", att_ecode);
+ return;
+ }
+
+ memcpy(&ip_status, value, length);
+
+ DBG(vcp, "Audio Input Status : %x", ip_status);
+}
+
+static void aics_ip_status_notify(struct bt_vcp *vcp, uint16_t value_handle,
+ const uint8_t *value,
+ uint16_t length,
+ void *user_data)
+{
+ uint8_t ip_status;
+
+ memcpy(&ip_status, value, length);
+
+ DBG(vcp, "Audio Input Status, %x", ip_status);
+}
+
+static void read_aics_audio_ip_description(struct bt_vcp *vcp, bool success,
+ uint8_t att_ecode,
+ const uint8_t *value,
+ uint16_t length,
+ void *user_data)
+{
+ char *ip_descrptn;
+
+ if (!value) {
+ DBG(vcp, "Unable to get Audio Input Description");
+ return;
+ }
+
+ if (!success) {
+ DBG(vcp,
+ "Unable to read Audio Input Description Char: error 0x%02x",
+ att_ecode);
+ return;
+ }
+
+ ip_descrptn = malloc(length+1);
+ memset(ip_descrptn, 0, length+1);
+ memcpy(ip_descrptn, value, length);
+
+ if (!ip_descrptn) {
+ DBG(vcp, "Unable to get Audio Input Description");
+ return;
+ }
+
+ DBG(vcp, "Audio Input Description: %s", ip_descrptn);
+ free(ip_descrptn);
+ ip_descrptn = NULL;
+}
+
+static void aics_audio_ip_desr_notify(struct bt_vcp *vcp, uint16_t value_handle,
+ const uint8_t *value, uint16_t length,
+ void *user_data)
+{
+ char *aud_ip_desr;
+
+ aud_ip_desr = malloc(length+1);
+ memset(aud_ip_desr, 0, length+1);
+ memcpy(aud_ip_desr, value, length);
+
+ DBG(vcp, "Audio Input Description Notify, %s", aud_ip_desr);
+ free(aud_ip_desr);
+ aud_ip_desr = NULL;
+}
+
+static void foreach_aics_char(struct gatt_db_attribute *attr, void *user_data)
+{
+ struct bt_vcp *vcp = user_data;
+ uint16_t value_handle;
+ bt_uuid_t uuid, uuid_ipstate, uuid_gain_setting_prop, uuid_ip_type,
+ uuid_ip_status, uuid_ip_cp, uuid_ip_decs;
+ struct bt_aics *aics;
+
+ if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle,
+ NULL, NULL, &uuid))
+ return;
+
+ bt_uuid16_create(&uuid_ipstate, AICS_INPUT_STATE_CHAR_UUID);
+ bt_uuid16_create(&uuid_gain_setting_prop,
+ AICS_GAIN_SETTING_PROP_CHAR_UUID);
+ bt_uuid16_create(&uuid_ip_type, AICS_AUDIO_INPUT_TYPE_CHAR_UUID);
+ bt_uuid16_create(&uuid_ip_status, AICS_INPUT_STATUS_CHAR_UUID);
+ bt_uuid16_create(&uuid_ip_cp, AICS_AUDIO_INPUT_CP_CHRC_UUID);
+ bt_uuid16_create(&uuid_ip_decs, AICS_INPUT_DESCR_CHAR_UUID);
+
+
+ if (!bt_uuid_cmp(&uuid, &uuid_ipstate)) {
+ DBG(vcp,
+ "AICS Audio Input State Char found: handle 0x%04x",
+ value_handle);
+
+ aics = vcp_get_aics(vcp);
+ if (!aics || aics->aud_ip_state)
+ return;
+
+ aics->aud_ip_state = attr;
+
+ vcp_read_value(vcp, value_handle,
+ read_aics_audio_ip_state, vcp);
+
+ vcp->aics_ip_state_id = vcp_register_notify(vcp, value_handle,
+ aics_ip_state_notify, NULL);
+
+ return;
+ }
+
+ if (!bt_uuid_cmp(&uuid, &uuid_gain_setting_prop)) {
+ DBG(vcp,
+ "AICS Gain Setting Properties Char found: handle 0x%04x",
+ value_handle);
+
+ aics = vcp_get_aics(vcp);
+ if (!aics || aics->gain_stting_prop)
+ return;
+
+ aics->gain_stting_prop = attr;
+
+ vcp_read_value(vcp, value_handle, read_aics_gain_setting_prop,
+ vcp);
+ return;
+ }
+
+ if (!bt_uuid_cmp(&uuid, &uuid_ip_type)) {
+ DBG(vcp, "AICS Audio Input Type Char found: handle 0x%04x",
+ value_handle);
+
+ aics = vcp_get_aics(vcp);
+ if (!aics || aics->gain_stting_prop)
+ return;
+
+ aics->aud_ip_type = attr;
+
+ vcp_read_value(vcp, value_handle, read_aics_aud_ip_type,
+ vcp);
+ return;
+ }
+
+ if (!bt_uuid_cmp(&uuid, &uuid_ip_status)) {
+ DBG(vcp,
+ "AICS Audio Input Status Char found: handle 0x%04x",
+ value_handle);
+
+ aics = vcp_get_aics(vcp);
+ if (!aics || aics->aud_ip_status)
+ return;
+
+ aics->aud_ip_status = attr;
+
+ vcp_read_value(vcp, value_handle,
+ read_aics_audio_ip_status, vcp);
+
+ vcp->aics_ip_status_id = vcp_register_notify(vcp, value_handle,
+ aics_ip_status_notify, NULL);
+
+ return;
+ }
+
+ if (!bt_uuid_cmp(&uuid, &uuid_ip_cp)) {
+ DBG(vcp, "AICS Input CP found: handle 0x%04x", value_handle);
+
+ aics = vcp_get_aics(vcp);
+ if (!aics || aics->aud_ip_cp)
+ return;
+
+ aics->aud_ip_cp = attr;
+
+ return;
+ }
+
+ if (!bt_uuid_cmp(&uuid, &uuid_ip_decs)) {
+ DBG(vcp,
+ "AICS Audio Input Description Char found: handle 0x%04x",
+ value_handle);
+
+ aics = vcp_get_aics(vcp);
+ if (!aics || aics->aud_ip_dscrptn)
+ return;
+
+ aics->aud_ip_dscrptn = attr;
+
+ vcp_read_value(vcp, value_handle,
+ read_aics_audio_ip_description, vcp);
+ vcp->aics_ip_descr_id = vcp_register_notify(vcp, value_handle,
+ aics_audio_ip_desr_notify, NULL);
+ }
+}
+
static void foreach_vcs_service(struct gatt_db_attribute *attr,
void *user_data)
{
@@ -1711,6 +2704,19 @@ static void foreach_vocs_service(struct gatt_db_attribute *attr,
gatt_db_service_foreach_char(attr, foreach_vocs_char, vcp);
}
+static void foreach_aics_service(struct gatt_db_attribute *attr,
+ void *user_data)
+{
+ struct bt_vcp *vcp = user_data;
+ struct bt_aics *aics = vcp_get_aics(vcp);
+
+ aics->service = attr;
+
+ gatt_db_service_set_claimed(attr, true);
+
+ gatt_db_service_foreach_char(attr, foreach_aics_char, vcp);
+}
+
bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client)
{
bt_uuid_t uuid;
@@ -1736,6 +2742,9 @@ bool bt_vcp_attach(struct bt_vcp *vcp, struct bt_gatt_client *client)
bt_uuid16_create(&uuid, VOL_OFFSET_CS_UUID);
gatt_db_foreach_service(vcp->rdb->db, &uuid, foreach_vocs_service, vcp);
+ bt_uuid16_create(&uuid, AUDIO_INPUT_CS_UUID);
+ gatt_db_foreach_service(vcp->rdb->db, &uuid, foreach_aics_service, vcp);
+
return true;
}