[PATCH BlueZ v2 1/4] shared/hfp: Add HF SLC connection function

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This implements the minimal SLC connection exchange, i.e. AT+BRSF,
AT+CIND=?, AT+CIND? and AT+CMER=3,0,0,1 requested to complete the
Service Level Connection Establishment.
---
 src/shared/hfp.c | 508 +++++++++++++++++++++++++++++++++++++++++++++++
 src/shared/hfp.h |  69 +++++++
 2 files changed, 577 insertions(+)

diff --git a/src/shared/hfp.c b/src/shared/hfp.c
index df6eab35d..c1bcb61cf 100644
--- a/src/shared/hfp.c
+++ b/src/shared/hfp.c
@@ -25,6 +25,12 @@
 #include "src/shared/io.h"
 #include "src/shared/hfp.h"
 
+#define DBG(_hfp, fmt, arg...) \
+	hfp_debug(_hfp->debug_callback, _hfp->debug_data, "%s:%s() " fmt, \
+						__FILE__, __func__, ## arg)
+
+#define HFP_HF_FEATURES	(HFP_HF_FEAT_ESCO_S4_T2)
+
 struct hfp_gw {
 	int ref_count;
 	int fd;
@@ -50,6 +56,16 @@ struct hfp_gw {
 	bool destroyed;
 };
 
+typedef void (*ciev_func_t)(uint8_t val, void *user_data);
+
+struct indicator {
+	uint8_t index;
+	uint32_t min;
+	uint32_t max;
+	uint32_t val;
+	ciev_func_t cb;
+};
+
 struct hfp_hf {
 	int ref_count;
 	int fd;
@@ -73,6 +89,17 @@ struct hfp_hf {
 
 	bool in_disconnect;
 	bool destroyed;
+
+	struct hfp_hf_callbacks *callbacks;
+	void *callbacks_data;
+
+	uint32_t features;
+	struct indicator ag_ind[HFP_INDICATOR_LAST];
+	bool service;
+	uint8_t signal;
+	bool roaming;
+	uint8_t battchg;
+
 };
 
 struct cmd_handler {
@@ -101,6 +128,19 @@ struct event_handler {
 	hfp_hf_result_func_t callback;
 };
 
+static void hfp_debug(hfp_debug_func_t debug_func, void *debug_data,
+						const char *format, ...)
+{
+	va_list ap;
+
+	if (!debug_func || !format)
+		return;
+
+	va_start(ap, format);
+	util_debug_va(debug_func, debug_data, format, ap);
+	va_end(ap);
+}
+
 static void destroy_cmd_handler(void *data)
 {
 	struct cmd_handler *handler = data;
@@ -1527,3 +1567,471 @@ bool hfp_hf_disconnect(struct hfp_hf *hfp)
 
 	return io_shutdown(hfp->io);
 }
+
+static void ciev_service_cb(uint8_t val, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "%u", val);
+
+	if (val < hfp->ag_ind[HFP_INDICATOR_SERVICE].min ||
+			val > hfp->ag_ind[HFP_INDICATOR_SERVICE].max) {
+		DBG(hfp, "hf: Incorrect state %u:", val);
+		return;
+	}
+
+	hfp->service = val;
+	if (hfp->callbacks && hfp->callbacks->update_indicator)
+		hfp->callbacks->update_indicator(HFP_INDICATOR_SERVICE, val,
+							hfp->callbacks_data);
+}
+
+static void ciev_call_cb(uint8_t val, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "%u", val);
+
+	if (val < hfp->ag_ind[HFP_INDICATOR_CALL].min ||
+			val > hfp->ag_ind[HFP_INDICATOR_CALL].max) {
+		DBG(hfp, "hf: Incorrect call state %u:", val);
+		return;
+	}
+}
+
+static void ciev_callsetup_cb(uint8_t val, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "%u", val);
+
+	if (val < hfp->ag_ind[HFP_INDICATOR_CALLSETUP].min ||
+			val > hfp->ag_ind[HFP_INDICATOR_CALLSETUP].max) {
+		DBG(hfp, "hf: Incorrect call setup state %u:", val);
+		return;
+	}
+}
+
+static void ciev_callheld_cb(uint8_t val, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "%u", val);
+
+	if (val < hfp->ag_ind[HFP_INDICATOR_CALLHELD].min ||
+			val > hfp->ag_ind[HFP_INDICATOR_CALLHELD].max) {
+		DBG(hfp, "hf: Incorrect call held state %u:", val);
+		return;
+	}
+}
+
+static void ciev_signal_cb(uint8_t val, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "%u", val);
+
+	if (val < hfp->ag_ind[HFP_INDICATOR_SIGNAL].min ||
+			val > hfp->ag_ind[HFP_INDICATOR_SIGNAL].max) {
+		DBG(hfp, "hf: Incorrect signal value %u:", val);
+		return;
+	}
+
+	hfp->signal = val;
+	if (hfp->callbacks && hfp->callbacks->update_indicator)
+		hfp->callbacks->update_indicator(HFP_INDICATOR_SIGNAL, val,
+							hfp->callbacks_data);
+}
+
+static void ciev_roam_cb(uint8_t val, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "%u", val);
+
+	if (val < hfp->ag_ind[HFP_INDICATOR_ROAM].min ||
+			val > hfp->ag_ind[HFP_INDICATOR_ROAM].max) {
+		DBG(hfp, "hf: Incorrect roaming state %u:", val);
+		return;
+	}
+
+	hfp->roaming = val;
+	if (hfp->callbacks && hfp->callbacks->update_indicator)
+		hfp->callbacks->update_indicator(HFP_INDICATOR_ROAM, val,
+							hfp->callbacks_data);
+}
+
+static void ciev_battchg_cb(uint8_t val, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "%u", val);
+
+	if (val < hfp->ag_ind[HFP_INDICATOR_BATTCHG].min ||
+			val > hfp->ag_ind[HFP_INDICATOR_BATTCHG].max) {
+		DBG(hfp, "hf: Incorrect battery charge value %u:", val);
+		return;
+	}
+
+	hfp->battchg = val;
+	if (hfp->callbacks && hfp->callbacks->update_indicator)
+		hfp->callbacks->update_indicator(HFP_INDICATOR_BATTCHG, val,
+							hfp->callbacks_data);
+}
+
+static void set_indicator_value(uint8_t index, unsigned int val,
+	struct indicator *ag_ind, struct hfp_hf *hfp)
+{
+	int i;
+
+	for (i = 0; i < HFP_INDICATOR_LAST; i++) {
+		if (index != ag_ind[i].index)
+			continue;
+
+		ag_ind[i].val = val;
+		ag_ind[i].cb(val, hfp);
+		return;
+	}
+}
+
+static void slc_cmer_resp(enum hfp_result result, enum hfp_error cme_err,
+	void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "");
+
+	if (result != HFP_RESULT_OK) {
+		DBG(hfp, "hf: CMER error: %d", result);
+		goto failed;
+	}
+
+	if (hfp->callbacks->session_ready)
+		hfp->callbacks->session_ready(HFP_RESULT_OK, 0,
+						hfp->callbacks_data);
+	return;
+
+failed:
+	if (hfp->callbacks->session_ready)
+		hfp->callbacks->session_ready(result, cme_err,
+						hfp->callbacks_data);
+}
+
+static void slc_cind_status_cb(struct hfp_context *context,
+	void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+	uint8_t index = 1;
+
+	while (hfp_context_has_next(context)) {
+		uint32_t val;
+
+		if (!hfp_context_get_number(context, &val)) {
+			DBG(hfp, "hf: Error on CIND status response");
+			return;
+		}
+
+		set_indicator_value(index++, val, hfp->ag_ind, hfp);
+	}
+}
+
+static void slc_cind_status_resp(enum hfp_result result,
+	enum hfp_error cme_err,
+	void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "");
+
+	hfp_hf_unregister(hfp, "+CIND");
+
+	if (result != HFP_RESULT_OK) {
+		DBG(hfp, "hf: CIND error: %d", result);
+		goto failed;
+	}
+
+	/* Continue with SLC creation */
+	if (!hfp_hf_send_command(hfp, slc_cmer_resp, hfp,
+		"AT+CMER=3,0,0,1")) {
+		DBG(hfp, "hf: Could not send AT+CMER");
+		result = HFP_RESULT_ERROR;
+		goto failed;
+	}
+
+	return;
+
+failed:
+	if (hfp->callbacks->session_ready)
+		hfp->callbacks->session_ready(result, cme_err,
+						hfp->callbacks_data);
+}
+
+static void set_indicator_parameters(struct hfp_hf *hfp, uint8_t index,
+	const char *indicator,
+	unsigned int min,
+	unsigned int max)
+{
+	struct indicator *ag_ind = hfp->ag_ind;
+
+	DBG(hfp, "%s, %i", indicator, index);
+
+	if (strcmp("service", indicator) == 0) {
+		if (min != 0 || max != 1) {
+			DBG(hfp, "hf: Invalid min/max values for service,"
+				" expected (0,1) got (%u,%u)", min, max);
+			return;
+		}
+		ag_ind[HFP_INDICATOR_SERVICE].index = index;
+		ag_ind[HFP_INDICATOR_SERVICE].min = min;
+		ag_ind[HFP_INDICATOR_SERVICE].max = max;
+		ag_ind[HFP_INDICATOR_SERVICE].cb = ciev_service_cb;
+		return;
+	}
+
+	if (strcmp("call", indicator) == 0) {
+		if (min != 0 || max != 1) {
+			DBG(hfp, "hf: Invalid min/max values for call,"
+				" expected (0,1) got (%u,%u)", min, max);
+			return;
+		}
+		ag_ind[HFP_INDICATOR_CALL].index = index;
+		ag_ind[HFP_INDICATOR_CALL].min = min;
+		ag_ind[HFP_INDICATOR_CALL].max = max;
+		ag_ind[HFP_INDICATOR_CALL].cb = ciev_call_cb;
+		return;
+	}
+
+	if (strcmp("callsetup", indicator) == 0) {
+		if (min != 0 || max != 3) {
+			DBG(hfp, "hf: Invalid min/max values for callsetup,"
+				" expected (0,3) got (%u,%u)", min, max);
+			return;
+		}
+		ag_ind[HFP_INDICATOR_CALLSETUP].index = index;
+		ag_ind[HFP_INDICATOR_CALLSETUP].min = min;
+		ag_ind[HFP_INDICATOR_CALLSETUP].max = max;
+		ag_ind[HFP_INDICATOR_CALLSETUP].cb = ciev_callsetup_cb;
+		return;
+	}
+
+	if (strcmp("callheld", indicator) == 0) {
+		if (min != 0 || max != 2) {
+			DBG(hfp, "hf: Invalid min/max values for callheld,"
+				" expected (0,2) got (%u,%u)", min, max);
+			return;
+		}
+		ag_ind[HFP_INDICATOR_CALLHELD].index = index;
+		ag_ind[HFP_INDICATOR_CALLHELD].min = min;
+		ag_ind[HFP_INDICATOR_CALLHELD].max = max;
+		ag_ind[HFP_INDICATOR_CALLHELD].cb = ciev_callheld_cb;
+		return;
+	}
+
+	if (strcmp("signal", indicator) == 0) {
+		if (min != 0 || max != 5) {
+			DBG(hfp, "hf: Invalid min/max values for signal,"
+				" expected (0,5) got (%u,%u)", min, max);
+			return;
+		}
+		ag_ind[HFP_INDICATOR_SIGNAL].index = index;
+		ag_ind[HFP_INDICATOR_SIGNAL].min = min;
+		ag_ind[HFP_INDICATOR_SIGNAL].max = max;
+		ag_ind[HFP_INDICATOR_SIGNAL].cb = ciev_signal_cb;
+		return;
+	}
+
+	if (strcmp("roam", indicator) == 0) {
+		if (min != 0 || max != 1) {
+			DBG(hfp, "hf: Invalid min/max values for roam,"
+				" expected (0,1) got (%u,%u)", min, max);
+			return;
+		}
+		ag_ind[HFP_INDICATOR_ROAM].index = index;
+		ag_ind[HFP_INDICATOR_ROAM].min = min;
+		ag_ind[HFP_INDICATOR_ROAM].max = max;
+		ag_ind[HFP_INDICATOR_ROAM].cb = ciev_roam_cb;
+		return;
+	}
+
+	if (strcmp("battchg", indicator) == 0) {
+		if (min != 0 || max != 5) {
+			DBG(hfp, "hf: Invalid min/max values for battchg,"
+				" expected (0,5) got (%u,%u)", min, max);
+			return;
+		}
+		ag_ind[HFP_INDICATOR_BATTCHG].index = index;
+		ag_ind[HFP_INDICATOR_BATTCHG].min = min;
+		ag_ind[HFP_INDICATOR_BATTCHG].max = max;
+		ag_ind[HFP_INDICATOR_BATTCHG].cb = ciev_battchg_cb;
+		return;
+	}
+
+	DBG(hfp, "hf: Unknown indicator: %s", indicator);
+}
+
+static void slc_cind_cb(struct hfp_context *context, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+	int index = 1;
+
+	DBG(hfp, "");
+
+	while (hfp_context_has_next(context)) {
+		char name[255];
+		unsigned int min, max;
+
+		/* e.g ("callsetup",(0-3)) */
+		if (!hfp_context_open_container(context))
+			break;
+
+		if (!hfp_context_get_string(context, name, sizeof(name))) {
+			DBG(hfp, "hf: Could not get string");
+			goto failed;
+		}
+
+		if (!hfp_context_open_container(context)) {
+			DBG(hfp, "hf: Could not open container");
+			goto failed;
+		}
+
+		if (!hfp_context_get_range(context, &min, &max)) {
+			if (!hfp_context_get_number(context, &min)) {
+				DBG(hfp, "hf: Could not get number");
+				goto failed;
+			}
+
+			if (!hfp_context_get_number(context, &max)) {
+				DBG(hfp, "hf: Could not get number");
+				goto failed;
+			}
+		}
+
+		if (!hfp_context_close_container(context)) {
+			DBG(hfp, "hf: Could not close container");
+			goto failed;
+		}
+
+		if (!hfp_context_close_container(context)) {
+			DBG(hfp, "hf: Could not close container");
+			goto failed;
+		}
+
+		set_indicator_parameters(hfp, index, name, min, max);
+		index++;
+	}
+
+	return;
+
+failed:
+	DBG(hfp, "hf: Error on CIND response");
+}
+
+static void slc_cind_resp(enum hfp_result result, enum hfp_error cme_err,
+	void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "");
+
+	hfp_hf_unregister(hfp, "+CIND");
+
+	if (result != HFP_RESULT_OK) {
+		DBG(hfp, "hf: CIND error: %d", result);
+		goto failed;
+	}
+
+	/* Continue with SLC creation */
+	if (!hfp_hf_register(hfp, slc_cind_status_cb, "+CIND", hfp,
+			NULL)) {
+		DBG(hfp, "hf: Could not register +CIND");
+		result = HFP_RESULT_ERROR;
+		goto failed;
+	}
+
+	if (!hfp_hf_send_command(hfp, slc_cind_status_resp, hfp,
+			"AT+CIND?")) {
+		DBG(hfp, "hf: Could not send AT+CIND?");
+		result = HFP_RESULT_ERROR;
+		goto failed;
+	}
+
+	return;
+
+failed:
+	if (hfp->callbacks->session_ready)
+		hfp->callbacks->session_ready(result, cme_err,
+						hfp->callbacks_data);
+}
+
+static void slc_brsf_cb(struct hfp_context *context, void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+	unsigned int feat;
+
+	DBG(hfp, "");
+
+	if (hfp_context_get_number(context, &feat))
+		hfp->features = feat;
+}
+
+static void slc_brsf_resp(enum hfp_result result, enum hfp_error cme_err,
+	void *user_data)
+{
+	struct hfp_hf *hfp = user_data;
+
+	DBG(hfp, "");
+
+	hfp_hf_unregister(hfp, "+BRSF");
+
+	if (result != HFP_RESULT_OK) {
+		DBG(hfp, "BRSF error: %d", result);
+		goto failed;
+	}
+
+	/* Continue with SLC creation */
+	if (!hfp_hf_register(hfp, slc_cind_cb, "+CIND", hfp, NULL)) {
+		DBG(hfp, "hf: Could not register for +CIND");
+		result = HFP_RESULT_ERROR;
+		goto failed;
+	}
+
+	if (!hfp_hf_send_command(hfp, slc_cind_resp, hfp, "AT+CIND=?")) {
+		DBG(hfp, "hf: Could not send AT+CIND command");
+		result = HFP_RESULT_ERROR;
+		goto failed;
+	}
+
+	return;
+
+failed:
+	if (hfp->callbacks->session_ready)
+		hfp->callbacks->session_ready(result, cme_err,
+						hfp->callbacks_data);
+}
+
+bool hfp_hf_session_register(struct hfp_hf *hfp,
+				struct hfp_hf_callbacks *callbacks,
+				void *callbacks_data)
+{
+	if (!hfp)
+		return false;
+
+	hfp->callbacks = callbacks;
+	hfp->callbacks_data = callbacks_data;
+
+	return true;
+}
+
+bool hfp_hf_session(struct hfp_hf *hfp)
+{
+	DBG(hfp, "");
+
+	if (!hfp)
+		return false;
+
+	if (!hfp_hf_register(hfp, slc_brsf_cb, "+BRSF", hfp, NULL))
+		return false;
+
+	return hfp_hf_send_command(hfp, slc_brsf_resp, hfp,
+					"AT+BRSF=%u", HFP_HF_FEATURES);
+}
diff --git a/src/shared/hfp.h b/src/shared/hfp.h
index 600d084a7..f54b86a92 100644
--- a/src/shared/hfp.h
+++ b/src/shared/hfp.h
@@ -10,6 +10,34 @@
 
 #include <stdbool.h>
 
+#define HFP_HF_FEAT_ECNR				0x00000001
+#define HFP_HF_FEAT_3WAY				0x00000002
+#define HFP_HF_FEAT_CLIP				0x00000004
+#define HFP_HF_FEAT_VOICE_RECOGNITION			0x00000008
+#define HFP_HF_FEAT_REMOTE_VOLUME_CONTROL		0x00000010
+#define HFP_HF_FEAT_ENHANCED_CALL_STATUS		0x00000020
+#define HFP_HF_FEAT_ENHANCED_CALL_CONTROL		0x00000040
+#define HFP_HF_FEAT_CODEC_NEGOTIATION			0x00000080
+#define HFP_HF_FEAT_HF_INDICATORS			0x00000100
+#define HFP_HF_FEAT_ESCO_S4_T2				0x00000200
+#define HFP_HF_FEAT_ENHANCED_VOICE_RECOGNITION_STATUS	0x00000400
+#define HFP_HF_FEAT_VOICE_RECOGNITION_TEXT		0x00000800
+
+#define HFP_AG_FEAT_3WAY				0x00000001
+#define HFP_AG_FEAT_ECNR				0x00000002
+#define HFP_AG_FEAT_VOICE_RECOGNITION			0x00000004
+#define HFP_AG_FEAT_IN_BAND_RING_TONE			0x00000008
+#define HFP_AG_FEAT_ATTACH_VOICE_TAG			0x00000010
+#define HFP_AG_FEAT_REJECT_CALL				0x00000020
+#define HFP_AG_FEAT_ENHANCED_CALL_STATUS		0x00000040
+#define HFP_AG_FEAT_ENHANCED_CALL_CONTROL		0x00000080
+#define HFP_AG_FEAT_EXTENDED_RES_CODE			0x00000100
+#define HFP_AG_FEAT_CODEC_NEGOTIATION			0x00000200
+#define HFP_AG_FEAT_HF_INDICATORS			0x00000400
+#define HFP_AG_FEAT_ESCO_S4_T2				0x00000800
+#define HFP_AG_FEAT_ENHANCED_VOICE_RECOGNITION_STATUS	0x00001000
+#define HFP_AG_FEAT_VOICE_RECOGNITION_TEXT		0x00001000
+
 enum hfp_result {
 	HFP_RESULT_OK		= 0,
 	HFP_RESULT_CONNECT	= 1,
@@ -57,6 +85,35 @@ enum hfp_gw_cmd_type {
 	HFP_GW_CMD_TYPE_COMMAND
 };
 
+enum hfp_indicator {
+	HFP_INDICATOR_SERVICE = 0,
+	HFP_INDICATOR_CALL,
+	HFP_INDICATOR_CALLSETUP,
+	HFP_INDICATOR_CALLHELD,
+	HFP_INDICATOR_SIGNAL,
+	HFP_INDICATOR_ROAM,
+	HFP_INDICATOR_BATTCHG,
+	HFP_INDICATOR_LAST
+};
+
+enum hfp_call {
+	CIND_CALL_NONE = 0,
+	CIND_CALL_IN_PROGRESS
+};
+
+enum hfp_call_setup {
+	CIND_CALLSETUP_NONE = 0,
+	CIND_CALLSETUP_INCOMING,
+	CIND_CALLSETUP_DIALING,
+	CIND_CALLSETUP_ALERTING
+};
+
+enum hfp_call_held {
+	CIND_CALLHELD_NONE = 0,
+	CIND_CALLHELD_HOLD_AND_ACTIVE,
+	CIND_CALLHELD_HOLD
+};
+
 struct hfp_context;
 
 typedef void (*hfp_result_func_t)(struct hfp_context *context,
@@ -128,6 +185,13 @@ typedef void (*hfp_response_func_t)(enum hfp_result result,
 
 struct hfp_hf;
 
+struct hfp_hf_callbacks {
+	void (*session_ready)(enum hfp_result result, enum hfp_error cme_err,
+							void *user_data);
+	void (*update_indicator)(enum hfp_indicator indicator, uint32_t val,
+							void *user_data);
+};
+
 struct hfp_hf *hfp_hf_new(int fd);
 
 struct hfp_hf *hfp_hf_ref(struct hfp_hf *hfp);
@@ -146,3 +210,8 @@ bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback,
 bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix);
 bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb,
 				void *user_data, const char *format, ...);
+
+bool hfp_hf_session_register(struct hfp_hf *hfp,
+				struct hfp_hf_callbacks *callbacks,
+				void *callbacks_data);
+bool hfp_hf_session(struct hfp_hf *hfp);
-- 
2.43.0





[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux