bluez5: telephony: Postpone AT commands if one is already in progress

To be sure that the AG reply correspond to the command sent, this
postpone the new command if previous reply (OK, ERROR or +CME) has
not yet been received.
The postponed command is sent on reception of the reply.
This commit is contained in:
Frédéric Danis 2024-12-11 10:56:20 +01:00
parent fc45aba032
commit 62645214a7

View file

@ -159,6 +159,11 @@ struct rfcomm_call_data {
struct spa_bt_telephony_call *call; struct spa_bt_telephony_call *call;
}; };
struct rfcomm_cmd {
struct spa_list link;
char* cmd;
};
struct rfcomm { struct rfcomm {
struct spa_list link; struct spa_list link;
struct spa_source source; struct spa_source source;
@ -189,6 +194,7 @@ struct rfcomm {
unsigned int hfp_hf_nrec:1; unsigned int hfp_hf_nrec:1;
unsigned int hfp_hf_clcc:1; unsigned int hfp_hf_clcc:1;
unsigned int hfp_hf_cme:1; unsigned int hfp_hf_cme:1;
unsigned int hfp_hf_cmd_in_progress:1;
unsigned int hfp_hf_in_progress:1; unsigned int hfp_hf_in_progress:1;
unsigned int chld_supported:1; unsigned int chld_supported:1;
enum hfp_hf_state hf_state; enum hfp_hf_state hf_state;
@ -197,6 +203,7 @@ struct rfcomm {
uint32_t cind_enabled_indicators; uint32_t cind_enabled_indicators;
char *hf_indicators[MAX_HF_INDICATORS]; char *hf_indicators[MAX_HF_INDICATORS];
struct spa_bt_telephony_ag *telephony_ag; struct spa_bt_telephony_ag *telephony_ag;
struct spa_list hfp_hf_commands;
#endif #endif
}; };
@ -345,7 +352,7 @@ static void rfcomm_free(struct rfcomm *rfcomm)
/* from HF/HS to AG */ /* from HF/HS to AG */
SPA_PRINTF_FUNC(2, 3) SPA_PRINTF_FUNC(2, 3)
static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, ...) static ssize_t rfcomm_send_cmd(struct rfcomm *rfcomm, const char *format, ...)
{ {
struct impl *backend = rfcomm->backend; struct impl *backend = rfcomm->backend;
char message[RFCOMM_MESSAGE_MAX_LENGTH + 1]; char message[RFCOMM_MESSAGE_MAX_LENGTH + 1];
@ -362,6 +369,14 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format,
if (len > RFCOMM_MESSAGE_MAX_LENGTH) if (len > RFCOMM_MESSAGE_MAX_LENGTH)
return -E2BIG; return -E2BIG;
if (rfcomm->hfp_hf_cmd_in_progress) {
spa_log_debug(backend->log, "Command in progress, postponing: %s", message);
struct rfcomm_cmd *cmd = calloc(1, sizeof(struct rfcomm_cmd));
cmd->cmd = strndup(message, len);
spa_list_append(&rfcomm->hfp_hf_commands, &cmd->link);
return 0;
}
spa_log_debug(backend->log, "RFCOMM >> %s", message); spa_log_debug(backend->log, "RFCOMM >> %s", message);
/* /*
@ -382,6 +397,8 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format,
spa_log_error(backend->log, "RFCOMM write error: %s", strerror(errno)); spa_log_error(backend->log, "RFCOMM write error: %s", strerror(errno));
} }
rfcomm->hfp_hf_cmd_in_progress = true;
return len; return len;
} }
@ -1283,23 +1300,23 @@ static bool hfp_hf_wait_for_reply(struct rfcomm *rfcomm, char *buf, size_t len)
ret = poll(fds, 1, 2000); ret = poll(fds, 1, 2000);
if (ret < 0) { if (ret < 0) {
spa_log_error(backend->log, "RFCOMM poll error: %s", strerror(errno)); spa_log_error(backend->log, "RFCOMM poll error: %s", strerror(errno));
return false; goto done;
} else if (ret == 0) { } else if (ret == 0) {
spa_log_error(backend->log, "RFCOMM poll timeout"); spa_log_error(backend->log, "RFCOMM poll timeout");
return false; goto done;
} }
if (fds[0].revents & (POLLHUP | POLLERR)) { if (fds[0].revents & (POLLHUP | POLLERR)) {
spa_log_info(backend->log, "lost RFCOMM connection."); spa_log_info(backend->log, "lost RFCOMM connection.");
rfcomm_free(rfcomm); rfcomm_free(rfcomm);
return false; goto done;
} }
if (fds[0].revents & POLLIN) { if (fds[0].revents & POLLIN) {
tmp_len = read(rfcomm->source.fd, tmp_buf, sizeof(tmp_buf) - 1); tmp_len = read(rfcomm->source.fd, tmp_buf, sizeof(tmp_buf) - 1);
if (tmp_len < 0) { if (tmp_len < 0) {
spa_log_error(backend->log, "RFCOMM read error: %s", strerror(errno)); spa_log_error(backend->log, "RFCOMM read error: %s", strerror(errno));
return false; goto done;
} }
tmp_buf[tmp_len] = '\0'; tmp_buf[tmp_len] = '\0';
@ -1332,6 +1349,18 @@ static bool hfp_hf_wait_for_reply(struct rfcomm *rfcomm, char *buf, size_t len)
} }
} }
done:
rfcomm->hfp_hf_cmd_in_progress = false;
if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) {
struct rfcomm_cmd *cmd;
cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link);
spa_list_remove(&cmd->link);
spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd);
rfcomm_send_cmd(rfcomm, "%s", cmd->cmd);
free(cmd->cmd);
free(cmd);
}
return reply_found; return reply_found;
} }
@ -2201,114 +2230,128 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
} }
rfcomm->hfp_hf_in_progress = false; rfcomm->hfp_hf_in_progress = false;
} else if (spa_strstartswith(token, "OK")) { } else if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") ||
switch(rfcomm->hf_state) { spa_strstartswith(token, "+CME ERROR:")) {
case hfp_hf_brsf: rfcomm->hfp_hf_cmd_in_progress = false;
if (rfcomm->codec_negotiation_supported) { if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) {
char buf[64]; struct rfcomm_cmd *cmd;
struct spa_strbuf str; cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link);
spa_list_remove(&cmd->link);
spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd);
rfcomm_send_cmd(rfcomm, "%s", cmd->cmd);
free(cmd->cmd);
free(cmd);
}
spa_strbuf_init(&str, buf, sizeof(buf)); if (spa_strstartswith(token, "OK")) {
spa_strbuf_append(&str, "1"); switch(rfcomm->hf_state) {
if (rfcomm->msbc_supported_by_hfp) case hfp_hf_brsf:
spa_strbuf_append(&str, ",2"); if (rfcomm->codec_negotiation_supported) {
if (rfcomm->lc3_supported_by_hfp) char buf[64];
spa_strbuf_append(&str, ",3"); struct spa_strbuf str;
rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf); spa_strbuf_init(&str, buf, sizeof(buf));
rfcomm->hf_state = hfp_hf_bac; spa_strbuf_append(&str, "1");
} else { if (rfcomm->msbc_supported_by_hfp)
spa_strbuf_append(&str, ",2");
if (rfcomm->lc3_supported_by_hfp)
spa_strbuf_append(&str, ",3");
rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf);
rfcomm->hf_state = hfp_hf_bac;
} else {
rfcomm_send_cmd(rfcomm, "AT+CIND=?");
rfcomm->hf_state = hfp_hf_cind1;
}
break;
case hfp_hf_bac:
rfcomm_send_cmd(rfcomm, "AT+CIND=?"); rfcomm_send_cmd(rfcomm, "AT+CIND=?");
rfcomm->hf_state = hfp_hf_cind1; rfcomm->hf_state = hfp_hf_cind1;
}
break;
case hfp_hf_bac:
rfcomm_send_cmd(rfcomm, "AT+CIND=?");
rfcomm->hf_state = hfp_hf_cind1;
break;
case hfp_hf_cind1:
rfcomm_send_cmd(rfcomm, "AT+CIND?");
rfcomm->hf_state = hfp_hf_cind2;
break;
case hfp_hf_cind2:
rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,1");
rfcomm->hf_state = hfp_hf_cmer;
break;
case hfp_hf_cmer:
if (rfcomm->hfp_hf_3way) {
rfcomm_send_cmd(rfcomm, "AT+CHLD=?");
rfcomm->hf_state = hfp_hf_chld;
break; break;
} case hfp_hf_cind1:
SPA_FALLTHROUGH; rfcomm_send_cmd(rfcomm, "AT+CIND?");
case hfp_hf_chld: rfcomm->hf_state = hfp_hf_cind2;
rfcomm_send_cmd(rfcomm, "AT+CLIP=1");
rfcomm->hf_state = hfp_hf_clip;
break;
case hfp_hf_clip:
if (rfcomm->chld_supported) {
rfcomm_send_cmd(rfcomm, "AT+CCWA=1");
rfcomm->hf_state = hfp_hf_ccwa;
break; break;
} case hfp_hf_cind2:
SPA_FALLTHROUGH; rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,1");
case hfp_hf_ccwa: rfcomm->hf_state = hfp_hf_cmer;
if (rfcomm->hfp_hf_cme) {
rfcomm_send_cmd(rfcomm, "AT+CMEE=1");
rfcomm->hf_state = hfp_hf_cmee;
break; break;
} case hfp_hf_cmer:
SPA_FALLTHROUGH; if (rfcomm->hfp_hf_3way) {
case hfp_hf_cmee: rfcomm_send_cmd(rfcomm, "AT+CHLD=?");
if (backend->hfp_disable_nrec && rfcomm->hfp_hf_nrec) { rfcomm->hf_state = hfp_hf_chld;
rfcomm_send_cmd(rfcomm, "AT+NREC=0"); break;
rfcomm->hf_state = hfp_hf_nrec;
break;
}
SPA_FALLTHROUGH;
case hfp_hf_nrec:
rfcomm->hf_state = hfp_hf_slc1;
rfcomm->slc_configured = true;
if (!rfcomm->codec_negotiation_supported) {
if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) {
// TODO: We should manage the missing transport
} else {
spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile);
} }
} SPA_FALLTHROUGH;
case hfp_hf_chld:
rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); rfcomm_send_cmd(rfcomm, "AT+CLIP=1");
rfcomm->telephony_ag->address = strdup(rfcomm->device->address); rfcomm->hf_state = hfp_hf_clip;
telephony_ag_set_callbacks(rfcomm->telephony_ag,
&telephony_ag_callbacks, rfcomm);
if (rfcomm->transport) {
rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec;
rfcomm->telephony_ag->transport.state = rfcomm->transport->state;
}
telephony_ag_register(rfcomm->telephony_ag);
if (rfcomm->hfp_hf_clcc) {
rfcomm_send_cmd(rfcomm, "AT+CLCC");
rfcomm->hf_state = hfp_hf_slc2;
break; break;
} else { case hfp_hf_clip:
// TODO: Create calls if CIND reports one during SLC setup if (rfcomm->chld_supported) {
} rfcomm_send_cmd(rfcomm, "AT+CCWA=1");
rfcomm->hf_state = hfp_hf_ccwa;
break;
}
SPA_FALLTHROUGH;
case hfp_hf_ccwa:
if (rfcomm->hfp_hf_cme) {
rfcomm_send_cmd(rfcomm, "AT+CMEE=1");
rfcomm->hf_state = hfp_hf_cmee;
break;
}
SPA_FALLTHROUGH;
case hfp_hf_cmee:
if (backend->hfp_disable_nrec && rfcomm->hfp_hf_nrec) {
rfcomm_send_cmd(rfcomm, "AT+NREC=0");
rfcomm->hf_state = hfp_hf_nrec;
break;
}
SPA_FALLTHROUGH;
case hfp_hf_nrec:
rfcomm->hf_state = hfp_hf_slc1;
rfcomm->slc_configured = true;
/* Report volume on SLC establishment */ if (!rfcomm->codec_negotiation_supported) {
SPA_FALLTHROUGH; if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) {
case hfp_hf_slc2: // TODO: We should manage the missing transport
if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) } else {
rfcomm->hf_state = hfp_hf_vgs; spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile);
break; }
case hfp_hf_vgs: }
rfcomm->hf_state = hfp_hf_slc1;
if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX)) rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0);
rfcomm->hf_state = hfp_hf_vgm; rfcomm->telephony_ag->address = strdup(rfcomm->device->address);
break; telephony_ag_set_callbacks(rfcomm->telephony_ag,
default: &telephony_ag_callbacks, rfcomm);
break; if (rfcomm->transport) {
rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec;
rfcomm->telephony_ag->transport.state = rfcomm->transport->state;
}
telephony_ag_register(rfcomm->telephony_ag);
if (rfcomm->hfp_hf_clcc) {
rfcomm_send_cmd(rfcomm, "AT+CLCC");
rfcomm->hf_state = hfp_hf_slc2;
break;
} else {
// TODO: Create calls if CIND reports one during SLC setup
}
/* Report volume on SLC establishment */
SPA_FALLTHROUGH;
case hfp_hf_slc2:
if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX))
rfcomm->hf_state = hfp_hf_vgs;
break;
case hfp_hf_vgs:
rfcomm->hf_state = hfp_hf_slc1;
if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX))
rfcomm->hf_state = hfp_hf_vgm;
break;
default:
break;
}
} }
} }
@ -3241,6 +3284,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
rfcomm->source.fd = spa_steal_fd(fd); rfcomm->source.fd = spa_steal_fd(fd);
rfcomm->source.mask = SPA_IO_IN; rfcomm->source.mask = SPA_IO_IN;
rfcomm->source.rmask = 0; rfcomm->source.rmask = 0;
spa_list_init(&rfcomm->hfp_hf_commands);
/* By default all indicators are enabled */ /* By default all indicators are enabled */
rfcomm->cind_enabled_indicators = 0xFFFFFFFF; rfcomm->cind_enabled_indicators = 0xFFFFFFFF;
memset(rfcomm->hf_indicators, 0, sizeof rfcomm->hf_indicators); memset(rfcomm->hf_indicators, 0, sizeof rfcomm->hf_indicators);