From 485a64642e8da895f1b71ccc3d8aee78f5645639 Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Sat, 20 Aug 2016 10:56:14 -0700 Subject: [PATCH 03/11] 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(-) --- a/src/modules/bluetooth/backend-native.c +++ b/src/modules/bluetooth/backend-native.c @@ -61,6 +61,43 @@ struct transport_data { #define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1" #define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" +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" @@ -109,6 +146,27 @@ static pa_dbus_pending* send_and_add_to_ 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; @@ -352,6 +410,61 @@ static void register_profile(pa_bluetoot 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; @@ -398,6 +511,8 @@ static void rfcomm_io_callback(pa_mainlo 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; } @@ -540,7 +655,9 @@ static DBusMessage *profile_new_connecti 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; @@ -558,9 +675,8 @@ static DBusMessage *profile_new_connecti 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)); --- a/src/modules/bluetooth/bluez5-util.c +++ b/src/modules/bluetooth/bluez5-util.c @@ -152,7 +152,10 @@ pa_bluetooth_transport *pa_bluetooth_tra 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; --- a/src/modules/bluetooth/bluez5-util.h +++ b/src/modules/bluetooth/bluez5-util.h @@ -84,7 +84,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;