summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Bottomley <James.Bottomley@HansenPartnership.com>2016-08-20 15:05:05 -0700
committerTanu Kaskinen <tanuk@iki.fi>2016-08-21 14:11:16 +0300
commitaf0432669d8c08c3413767cfb9d22e8ac51b0522 (patch)
tree902457d095ab97814d095cacb081245104173f0e
parentd5186a1102f6dd3520d6ef70d7b788323be670c3 (diff)
bluetooth: add correct HFP rfcomm negotiationhfp
HFP 1.6 requires a stateful negotiation of AT commands. The prior version got away with initialising HFP simply by replying 'OK' to every negotiation attempt. This one actually tries to parse the state and make sure the negotiation occurs correctly Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
-rw-r--r--src/modules/bluetooth/backend-native.c139
-rw-r--r--src/modules/bluetooth/bluez5-util.c5
-rw-r--r--src/modules/bluetooth/bluez5-util.h2
-rw-r--r--src/pulsecore/core-util.h7
4 files changed, 141 insertions, 12 deletions
diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c
index f7453a4a..c7a4aa1d 100644
--- a/src/modules/bluetooth/backend-native.c
+++ b/src/modules/bluetooth/backend-native.c
@@ -50,6 +50,45 @@ struct transport_rfcomm {
pa_mainloop_api *mainloop;
};
+struct hfp_config {
+ uint32_t capabilities;
+ int state;
+};
+
+/*
+ * the separate hansfree headset (HF) and Audio Gateway (AG) features
+ */
+enum hfp_hf_features {
+ HFP_HF_EC_NR = 0,
+ HFP_HF_CALL_WAITING = 1,
+ HFP_HF_CLI = 2,
+ HFP_HF_VR = 3,
+ HFP_HF_RVOL = 4,
+ HFP_HF_ESTATUS = 5,
+ HFP_HF_ECALL = 6,
+ HFP_HF_CODECS = 7,
+};
+
+enum hfp_ag_features {
+ HFP_AG_THREE_WAY = 0,
+ HFP_AG_EC_NR = 1,
+ HFP_AG_VR = 2,
+ HFP_AG_RING = 3,
+ HFP_AG_NUM_TAG = 4,
+ HFP_AG_REJECT = 5,
+ HFP_AG_ESTATUS = 6,
+ HFP_AG_ECALL = 7,
+ HFP_AG_EERR = 8,
+ HFP_AG_CODECS = 9,
+};
+
+/* gateway features we support */
+static uint32_t hfp_features =
+ /* LG HBS900 reboots if it doesn't see this */
+ (1 << HFP_AG_THREE_WAY) |
+ /* HFP 1.6 requires this */
+ (1 << HFP_AG_ESTATUS );
+
#define BLUEZ_SERVICE "org.bluez"
#define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1"
@@ -101,6 +140,27 @@ static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_backend *backend, D
return p;
}
+static void rfcomm_write(int fd, const char *str)
+{
+ size_t len;
+ char buf[512];
+
+ pa_log_debug("RFCOMM >> %s", str);
+ sprintf(buf, "\r\n%s\r\n", str);
+ len = write(fd, buf, strlen(buf));
+
+ if (len != strlen(buf))
+ pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
+}
+
+static void hfp_send_features(int fd)
+{
+ char buf[512];
+
+ sprintf(buf, "+BRSF: %d", hfp_features);
+ rfcomm_write(fd, buf);
+}
+
static int bluez5_sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu, size_t *omtu) {
pa_bluetooth_device *d = t->device;
struct sockaddr_sco addr;
@@ -216,6 +276,58 @@ static void register_profile(pa_bluetooth_backend *b, const char *profile, const
send_and_add_to_pending(b, m, register_profile_reply, pa_xstrdup(profile));
}
+static void transport_put(pa_bluetooth_transport *t)
+{
+ pa_bluetooth_transport_put(t);
+
+ pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
+}
+
+static int hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf)
+{
+ struct hfp_config *c = t->config;
+ int val;
+
+ /* stateful negotiation */
+ if (c->state == 0 && sscanf(buf, "AT+BRSF=%d", &val) == 1) {
+ c->capabilities = val;
+ pa_log_info("HFP capabilities returns 0x%x", val);
+ hfp_send_features(fd);
+ c->state = 1;
+ return 0;
+ } else if (c->state == 1 && pa_strcont("AT+CIND=?", buf)) {
+ /* we declare minimal indicators */
+ rfcomm_write(fd, "+CIND: (\"call\",(0,1))");
+ c->state = 2;
+ return 0;
+ } else if (c->state == 2 && pa_strcont("AT+CIND?", buf)) {
+ rfcomm_write(fd, "+CIND: 0");
+ c->state = 3;
+ return 0;
+ } else if (c->state == 3 && pa_strcont("AT+CMER=", buf)) {
+ rfcomm_write(fd, "OK");
+ c->state = 4;
+ transport_put(t);
+ return 1;
+ }
+
+
+ if (c->state != 4)
+ goto error;
+
+ /* misc things to handle in fully connected state */
+
+ if (pa_strcont("AT+BTRH?", buf)) {
+ return 0;
+ } else if (pa_strcont("AT+CHLD=?", buf)) {
+ return 0;
+ }
+
+ error:
+ rfcomm_write(fd, "ERROR");
+ return 1;
+}
+
static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
pa_bluetooth_transport *t = userdata;
@@ -246,16 +358,18 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i
} else if (sscanf(buf, "AT+VGM=%d", &gain) == 1) {
t->microphone_gain = gain;
pa_hook_fire(pa_bluetooth_discovery_hook(t->device->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_MICROPHONE_GAIN_CHANGED), t);
- }
-
- pa_log_debug("RFCOMM >> OK");
-
- len = write(fd, "\r\nOK\r\n", 6);
+ } else if (t->config) {
+ switch (hfp_rfcomm_handle(fd, t, buf)) {
+ case -1:
+ goto fail;
+ case 1:
+ return;
+ }
+ }
+ rfcomm_write(fd, "OK");
/* we ignore any errors, it's not critical and real errors should
* be caught with the HANGUP and ERROR events handled above */
- if (len < 0)
- pa_log_error("RFCOMM write error: %s", pa_cstrerror(errno));
}
return;
@@ -365,7 +479,7 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
p = PA_BLUETOOTH_PROFILE_HSP_HS;
pathfd = pa_sprintf_malloc ("%s/fd%d", path, fd);
- t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, 0);
+ t = pa_bluetooth_transport_new(d, sender, pathfd, p, NULL, is_hfp ? sizeof(struct hfp_config) : 0);
pa_xfree(pathfd);
t->acquire = bluez5_sco_acquire_cb;
@@ -381,9 +495,14 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
rfcomm_io_callback, t);
t->userdata = trfc;
- pa_bluetooth_transport_put(t);
+ if (!is_hfp)
+ transport_put(t);
+ else {
+ pa_log_debug("beginning sleep");
+ sleep(1);
+ pa_log_debug("ending sleep");
+ }
- pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
pa_assert_se(r = dbus_message_new_method_return(m));
diff --git a/src/modules/bluetooth/bluez5-util.c b/src/modules/bluetooth/bluez5-util.c
index e4a50a7d..9e2cc53a 100644
--- a/src/modules/bluetooth/bluez5-util.c
+++ b/src/modules/bluetooth/bluez5-util.c
@@ -151,7 +151,10 @@ pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const
if (size > 0) {
t->config = pa_xnew(uint8_t, size);
- memcpy(t->config, config, size);
+ if (config)
+ memcpy(t->config, config, size);
+ else
+ memset(t->config, 0, size);
}
return t;
diff --git a/src/modules/bluetooth/bluez5-util.h b/src/modules/bluetooth/bluez5-util.h
index 73fe5982..7780a8d5 100644
--- a/src/modules/bluetooth/bluez5-util.h
+++ b/src/modules/bluetooth/bluez5-util.h
@@ -73,7 +73,7 @@ struct pa_bluetooth_transport {
pa_bluetooth_profile_t profile;
uint8_t codec;
- uint8_t *config;
+ void *config;
size_t config_size;
uint16_t microphone_gain;
diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
index 5725ca78..5b7ccda6 100644
--- a/src/pulsecore/core-util.h
+++ b/src/pulsecore/core-util.h
@@ -227,6 +227,13 @@ static inline bool pa_safe_streq(const char *a, const char *b) {
return pa_streq(a, b);
}
+/* see if the string b contains a as a prefix */
+static inline bool pa_strcont(const char *a, const char *b) {
+ if (a == NULL || b == NULL)
+ return a == b;
+ return !strncmp(a, b, strlen(a));
+}
+
bool pa_str_in_list_spaces(const char *needle, const char *haystack);
char *pa_get_host_name_malloc(void);