2023-11-29 13:41:39

by Mahesh Talewad

[permalink] [raw]
Subject: [PATCH BlueZ v1 0/2] Implementation of AICS

Hello Maintainers,

This Patch contains code for implementation of Service - AICS.
- This code covers all mandatory features of AICS.
- Verification: All mandatory PTS testcases of AICS are passed.
- Specification referred for implementation:
AICS_v1.0.pdf

Thank you in advance for your review.

Thanks and regards,
Mahesh Vithal Talewad

Mahesh Talewad (2):
- Added AICS Characteristics UUID(s).
- Code Implementation related Service- AICS. - Specification referred
for implementation: AICS_v1.0.pdf - Verification: Tested all
Mandatory PTS testcases and all mandatory testcases passed.

lib/uuid.h | 7 +
src/shared/vcp.c | 1009 +++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 1015 insertions(+), 1 deletion(-)

--
2.34.1



2023-11-29 13:41:56

by Mahesh Talewad

[permalink] [raw]
Subject: [PATCH BlueZ v1 1/2] - Added AICS Characteristics UUID(s).

---
lib/uuid.h | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/lib/uuid.h b/lib/uuid.h
index e682547aa..8839dea08 100644
--- a/lib/uuid.h
+++ b/lib/uuid.h
@@ -187,6 +187,13 @@ extern "C" {
#define VOCS_CP_CHRC_UUID 0x2B82
#define VOCS_AUDIO_OP_DESC_CHAR_UUID 0x2B83

+#define AICS_INPUT_STATE_CHAR_UUID 0x2B77
+#define AICS_GAIN_SETTING_PROP_CHAR_UUID 0x2B78
+#define AICS_AUDIO_INPUT_TYPE_CHAR_UUID 0x2B79
+#define AICS_INPUT_STATUS_CHAR_UUID 0X2B7A
+#define AICS_AUDIO_INPUT_CP_CHRC_UUID 0X2B7B
+#define AICS_INPUT_DESCR_CHAR_UUID 0X2B7C
+
#define GMCS_UUID 0x1849
#define MEDIA_PLAYER_NAME_CHRC_UUID 0x2b93
#define MEDIA_TRACK_CHNGD_CHRC_UUID 0x2b96
--
2.34.1


2023-11-29 13:41:59

by Mahesh Talewad

[permalink] [raw]
Subject: [PATCH BlueZ v1 2/2] - Code Implementation related Service- AICS. - Specification referred for implementation: AICS_v1.0.pdf - Verification: Tested all Mandatory PTS testcases and all mandatory testcases passed.

---
src/shared/vcp.c | 1009 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 1008 insertions(+), 1 deletion(-)

diff --git a/src/shared/vcp.c b/src/shared/vcp.c
index 263da4c30..cf919526b 100644
--- a/src/shared/vcp.c
+++ b/src/shared/vcp.c
@@ -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_NUMB_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 0.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);
}

@@ -980,6 +1081,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 val 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)
+{
+ /* AICS optional feature */
+}
+
static struct bt_vcs *vcs_new(struct gatt_db *db, struct bt_vcp_db *vdb)
{
struct bt_vcs *vcs;
@@ -998,9 +1581,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_NUMB_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,
@@ -1109,6 +1695,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;
@@ -1122,6 +1810,8 @@ static struct bt_vcp_db *vcp_db_new(struct gatt_db *db)
if (!vcp_db)
vcp_db = queue_new();

+ vdb->aics = aics_new(db);
+ vdb->aics->vdb = vdb;
vdb->vocs = vocs_new(db);
vdb->vocs->vdb = vdb;
vdb->vcs = vcs_new(db, vdb);
@@ -1676,6 +2366,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 AICS Gain Setting Properties Char");
+ return;
+ }
+
+ if (!success) {
+ DBG(vcp,
+ "Unable to read Gain Setting Prop 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 AICS 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 Ip Type Char: error 0x%02x",
+ att_ecode);
+ return;
+ }
+
+ memcpy(&ip_type, value, length);
+
+ DBG(vcp, "AICS 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 Ip Status Char: 0x%02x", att_ecode);
+ return;
+ }
+
+ memcpy(&ip_status, value, length);
+
+ DBG(vcp, "AICS 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 AICS Audio Input Description");
+ return;
+ }
+
+ if (!success) {
+ DBG(vcp,
+ "Unable to read Audio Ip Descrn 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 AICS Audio Input Description");
+ return;
+ }
+
+ DBG(vcp, "AICS 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,
+ "Audio Ip 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,
+ "Gain Setting Props 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,
+ "Audio Ip 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,
+ "Audio Ip Descrpn 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)
{
@@ -1702,6 +2693,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;
@@ -1727,6 +2731,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;
}

--
2.34.1


2023-11-29 15:09:17

by bluez.test.bot

[permalink] [raw]
Subject: RE: Implementation of AICS

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=805284

---Test result---

Test Summary:
CheckPatch PASS 1.44 seconds
GitLint FAIL 0.83 seconds
BuildEll PASS 24.13 seconds
BluezMake PASS 583.27 seconds
MakeCheck FAIL 10.69 seconds
MakeDistcheck FAIL 135.05 seconds
CheckValgrind FAIL 210.55 seconds
CheckSmatch PASS 316.46 seconds
bluezmakeextell PASS 96.49 seconds
IncrementalBuild PASS 1049.14 seconds
ScanBuild PASS 914.51 seconds

Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,v1,1/2] - Added AICS Characteristics UUID(s).

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
1: T3 Title has trailing punctuation (.): "[BlueZ,v1,1/2] - Added AICS Characteristics UUID(s)."
[BlueZ,v1,2/2] - Code Implementation related Service- AICS. - Specification referred for implementation: AICS_v1.0.pdf - Verification: Tested all Mandatory PTS testcases and all mandatory testcases passed.

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
1: T1 Title exceeds max length (205>80): "[BlueZ,v1,2/2] - Code Implementation related Service- AICS. - Specification referred for implementation: AICS_v1.0.pdf - Verification: Tested all Mandatory PTS testcases and all mandatory testcases passed."
1: T3 Title has trailing punctuation (.): "[BlueZ,v1,2/2] - Code Implementation related Service- AICS. - Specification referred for implementation: AICS_v1.0.pdf - Verification: Tested all Mandatory PTS testcases and all mandatory testcases passed."
##############################
Test: MakeCheck - FAIL
Desc: Run Bluez Make Check
Output:

./test-driver: line 107: 31903 Aborted (core dumped) "$@" > $log_file 2>&1
make[3]: *** [Makefile:11633: test-suite.log] Error 1
make[2]: *** [Makefile:11741: check-TESTS] Error 2
make[1]: *** [Makefile:12170: check-am] Error 2
make: *** [Makefile:12172: check] Error 2
##############################
Test: MakeDistcheck - FAIL
Desc: Run Bluez Make Distcheck
Output:

../../test-driver: line 107: 54113 Aborted (core dumped) "$@" > $log_file 2>&1
make[4]: *** [Makefile:11633: test-suite.log] Error 1
make[3]: *** [Makefile:11741: check-TESTS] Error 2
make[2]: *** [Makefile:12170: check-am] Error 2
make[1]: *** [Makefile:12172: check] Error 2
make: *** [Makefile:12093: distcheck] Error 1
##############################
Test: CheckValgrind - FAIL
Desc: Run Bluez Make Check with Valgrind
Output:

tools/mgmt-tester.c: In function ‘main’:
tools/mgmt-tester.c:12763:5: note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying without
12763 | int main(int argc, char *argv[])
| ^~~~
./test-driver: line 107: 73831 Aborted (core dumped) "$@" > $log_file 2>&1
make[3]: *** [Makefile:11633: test-suite.log] Error 1
make[2]: *** [Makefile:11741: check-TESTS] Error 2
make[1]: *** [Makefile:12170: check-am] Error 2
make: *** [Makefile:12172: check] Error 2


---
Regards,
Linux Bluetooth

2023-12-18 20:50:40

by patchwork-bot+bluetooth

[permalink] [raw]
Subject: Re: [PATCH BlueZ v1 0/2] Implementation of AICS

Hello:

This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <[email protected]>:

On Wed, 29 Nov 2023 15:40:56 +0200 you wrote:
> Hello Maintainers,
>
> This Patch contains code for implementation of Service - AICS.
> - This code covers all mandatory features of AICS.
> - Verification: All mandatory PTS testcases of AICS are passed.
> - Specification referred for implementation:
> AICS_v1.0.pdf
>
> [...]

Here is the summary with links:
- [BlueZ,v1,1/2] - Added AICS Characteristics UUID(s).
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=c89ebba80f27
- [BlueZ,v1,2/2] - Code Implementation related Service- AICS. - Specification referred for implementation: AICS_v1.0.pdf - Verification: Tested all Mandatory PTS testcases and all mandatory testcases passed.
(no matching commit)

You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html