From 485a64642e8da895f1b71ccc3d8aee78f5645639 Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Sat, 20 Aug 2016 10:56:14 -0700 Subject: [PATCH] bluetooth: add correct HFP rfcomm negotiation 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 --- v4: - Update for PA 11.0 - Finally sort out CIND negotiaton for complex headsets v3: - remove internal debugging - added comment for t->config being not null for hfp - removed unused returns from hfp_rfcomm_handle() - remove rfcomm comment - use pa_startswith - simplify negotiation Part-of: --- src/modules/bluetooth/backend-native.c | 124 ++++++++++++++++++++++++- src/modules/bluetooth/bluez5-util.c | 5 +- src/modules/bluetooth/bluez5-util.h | 2 +- 3 files changed, 125 insertions(+), 6 deletions(-) diff --git a/src/modules/bluetooth/backend-native.c b/src/modules/bluetooth/backend-native.c index 653767cd7..4b6ae95c6 100644 --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -53,6 +53,43 @@ struct transport_data { 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, which is as little as we can get away with */ +static uint32_t hfp_features = + /* HFP 1.6 requires this */ + (1 << HFP_AG_ESTATUS ); + #define HSP_AG_PROFILE "/Profile/HSPAGProfile" #define HFP_AG_PROFILE "/Profile/HFPAGProfile" #define HSP_HS_PROFILE "/Profile/HSPHSProfile" @@ -101,6 +138,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 sco_do_connect(pa_bluetooth_transport *t) { pa_bluetooth_device *d = t->device; struct sockaddr_sco addr; @@ -348,6 +406,61 @@ 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 bool 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 true; + } else if (c->state == 1 && pa_startswith(buf, "AT+CIND=?")) { + /* we declare minimal no indicators */ + rfcomm_write(fd, "+CIND: " + /* many indicators can be supported, only call and + * callheld are mandatory, so that's all we repy */ + "(\"call\",(0-1))," + "(\"callheld\",(0-2))"); + c->state = 2; + return true; + } else if (c->state == 2 && pa_startswith(buf, "AT+CIND?")) { + rfcomm_write(fd, "+CIND: 0,0"); + c->state = 3; + return true; + } else if ((c->state == 2 || c->state == 3) && pa_startswith(buf, "AT+CMER=")) { + rfcomm_write(fd, "\r\nOK\r\n"); + c->state = 4; + transport_put(t); + return false; + } + + /* if we get here, negotiation should be complete */ + if (c->state != 4) { + pa_log_error("HFP negotiation failed in state %d with inbound %s\n", + c->state, buf); + rfcomm_write(fd, "ERROR"); + return false; + } + + /* + * once we're fully connected, just reply OK to everything + * it will just be the headset sending the occasional status + * update, but we process only the ones we care about + */ + return true; +} + 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; @@ -394,6 +507,8 @@ static void rfcomm_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_i do_reply = true; } else if (sscanf(buf, "AT+CKPD=%d", &dummy) == 1) { do_reply = true; + } else if (t->config) { /* t->config is only non-null for hfp profile */ + do_reply = hfp_rfcomm_handle(fd, t, buf); } else { do_reply = false; } @@ -536,7 +651,9 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, sender = dbus_message_get_sender(m); 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, + p == PA_BLUETOOTH_PROFILE_HFP_HF ? + sizeof(struct hfp_config) : 0); pa_xfree(pathfd); t->acquire = sco_acquire_cb; @@ -554,9 +671,8 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m, sco_listen(t); - pa_bluetooth_transport_put(t); - - pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile)); + if (p != PA_BLUETOOTH_PROFILE_HFP_HF) + transport_put(t); 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 b97787c46..990372166 100644 --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -172,7 +172,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 eea3498f6..6d2ff33cc 100644 --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -97,7 +97,7 @@ struct pa_bluetooth_transport { pa_bluetooth_profile_t profile; uint8_t codec; - uint8_t *config; + void *config; size_t config_size; const pa_a2dp_codec *a2dp_codec;