Hi Frédéric, On Tue, Aug 19, 2025 at 9:39 AM Frédéric Danis <frederic.danis@xxxxxxxxxxxxx> wrote: > > 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 Incremental build seem to be broken: git rebase -i origin/master --exec="make -j check" FAIL: unit/test-hfp > -- Luiz Augusto von Dentz