diff --git a/include/zephyr/bluetooth/classic/hfp_ag.h b/include/zephyr/bluetooth/classic/hfp_ag.h index 6e5173d9fa4c1..599a1ba1aaf44 100644 --- a/include/zephyr/bluetooth/classic/hfp_ag.h +++ b/include/zephyr/bluetooth/classic/hfp_ag.h @@ -42,6 +42,40 @@ enum bt_hfp_ag_indicator { #define BT_HFP_AG_CODEC_LC3_SWB 0x03 struct bt_hfp_ag; +struct bt_hfp_ag_call; + +/** @typedef bt_hfp_ag_query_subscriber_func_t + * @brief Query subscriber number callback function + * + * When AG wants to send subscriber number information, all information + * will be passed through the callback. And the subscriber number + * information will be sent out in this function. + * + * @param ag HFP AG object. + * @param number Subscriber number. + * @param type Type of subscriber number specifies the format of the phone number provided, + * and can be one of the following values: + * - values 128-143: The phone number format may be a national or international + * format, and may contain prefix and/or escape digits. No changes on the number + * presentation are required. + * - values 144-159: The phone number format is an international number, including + * the country code prefix. If the plus sign ("+") is not included as part of the + * number and shall be added by the AG as needed. + * - values 160-175: National number. No prefix nor escape digits included. + * @param service Service of subscriber number indicates which service this phone number relates + * to. Shall be either 4 (voice) or 5 (fax). + * + * @return 0 if should continue to the next subscriber number information. + * @return negative value to stop. + */ +typedef int (*bt_hfp_ag_query_subscriber_func_t)(struct bt_hfp_ag *ag, char *number, uint8_t type, + uint8_t service); + +/* HF indicators */ +enum hfp_ag_hf_indicators { + HFP_AG_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ + HFP_AG_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */ +}; /** @brief HFP profile AG application callback */ struct bt_hfp_ag_cb { @@ -50,9 +84,10 @@ struct bt_hfp_ag_cb { * If this callback is provided it will be called whenever the * AG connection completes. * + * @param conn Connection object. * @param ag HFP AG object. */ - void (*connected)(struct bt_hfp_ag *ag); + void (*connected)(struct bt_conn *conn, struct bt_hfp_ag *ag); /** HF disconnected callback to application * * If this callback is provided it will be called whenever the @@ -84,26 +119,43 @@ struct bt_hfp_ag_cb { /** HF memory dialing request Callback * * If this callback is provided it will be called whenever a - * new call is requested with memory dialing from HFP unit. + * new call is requested with memory dialing from HF. * Get the phone number according to the given AG memory location. * * @param ag HFP AG object. * @param location AG memory location - * @param number Dailing number + * @param number Dialing number * * @return 0 in case of success or negative value in case of error. */ int (*memory_dial)(struct bt_hfp_ag *ag, const char *location, char **number); + /** HF phone number calling request Callback + * + * If this callback is provided it will be called whenever a + * new call is requested with specific phone number from HF. + * When the callback is triggered, the application needs to start + * dialing the number with the passed phone number. + * If the callback is invalid, the phone number dialing from HF + * cannot be supported. + * + * @param ag HFP AG object. + * @param number Dialing number + * + * @return 0 in case of success or negative value in case of error. + */ + int (*number_call)(struct bt_hfp_ag *ag, const char *number); + /** HF outgoing Callback * * If this callback is provided it will be called whenever a * new call is outgoing. * * @param ag HFP AG object. - * @param number Dailing number + * @param call HFP AG call object. + * @param number Dialing number */ - void (*outgoing)(struct bt_hfp_ag *ag, const char *number); + void (*outgoing)(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number); /** HF incoming Callback * @@ -111,46 +163,74 @@ struct bt_hfp_ag_cb { * new call is incoming. * * @param ag HFP AG object. + * @param call HFP AG call object. * @param number Incoming number */ - void (*incoming)(struct bt_hfp_ag *ag, const char *number); + void (*incoming)(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number); + + /** HF incoming call is held Callback + * + * If this callback is provided it will be called whenever the + * incoming call is held but not accepted. + * + * @param call HFP AG call object. + */ + void (*incoming_held)(struct bt_hfp_ag_call *call); /** HF ringing Callback * * If this callback is provided it will be called whenever the * call is in the ringing * - * @param ag HFP AG object. + * @param call HFP AG call object. * @param in_bond true - in-bond ringing, false - No in-bond ringing */ - void (*ringing)(struct bt_hfp_ag *ag, bool in_band); + void (*ringing)(struct bt_hfp_ag_call *call, bool in_band); /** HF call accept Callback * * If this callback is provided it will be called whenever the * call is accepted. * - * @param ag HFP AG object. + * @param call HFP AG call object. */ - void (*accept)(struct bt_hfp_ag *ag); + void (*accept)(struct bt_hfp_ag_call *call); + + /** HF call held Callback + * + * If this callback is provided it will be called whenever the + * call is held. + * + * @param call HFP AG call object. + */ + void (*held)(struct bt_hfp_ag_call *call); + + /** HF call retrieve Callback + * + * If this callback is provided it will be called whenever the + * call is retrieved. + * + * @param call HFP AG call object. + */ + void (*retrieve)(struct bt_hfp_ag_call *call); /** HF call reject Callback * * If this callback is provided it will be called whenever the * call is rejected. * - * @param ag HFP AG object. + * @param call HFP AG call object. */ - void (*reject)(struct bt_hfp_ag *ag); + void (*reject)(struct bt_hfp_ag_call *call); /** HF call terminate Callback * * If this callback is provided it will be called whenever the * call is terminated. * - * @param ag HFP AG object. + * @param call HFP AG call object. */ - void (*terminate)(struct bt_hfp_ag *ag); + void (*terminate)(struct bt_hfp_ag_call *call); /** Supported codec Ids callback * @@ -160,6 +240,175 @@ struct bt_hfp_ag_cb { * @param ag HFP AG object. */ void (*codec)(struct bt_hfp_ag *ag, uint32_t ids); + + /** Codec negotiate callback + * + * If this callback is provided it will be called whenever the + * codec negotiation succeeded or failed. + * + * @param ag HFP AG object. + * @param err Result of codec negotiation. + */ + void (*codec_negotiate)(struct bt_hfp_ag *ag, int err); + + /** Audio connection request callback + * + * If this callback is provided it will be called whenever the + * audio conenction request is triggered by HF. + * When AT+BCC AT command received, it means the procedure of + * establishment of audio connection is triggered by HF. + * If the callback is provided by application, AG needs to + * start the codec connection procedure by calling + * function `bt_hfp_ag_audio_connect` in application layer. + * Or, the codec conenction procedure will be started with + * default codec id `BT_HFP_AG_CODEC_CVSD`. + * + * @param ag HFP AG object. + * @param err Result of codec negotiation. + */ + void (*audio_connect_req)(struct bt_hfp_ag *ag); + + /** HF VGM setting callback + * + * If this callback is provided it will be called whenever the + * VGM gain setting is informed from HF. + * + * @param ag HFP AG object. + * @param gain HF microphone gain value. + */ + void (*vgm)(struct bt_hfp_ag *ag, uint8_t gain); + + /** HF VGS setting callback + * + * If this callback is provided it will be called whenever the + * VGS gain setting is informed from HF. + * + * @param ag HFP AG object. + * @param gain HF speaker gain value. + */ + void (*vgs)(struct bt_hfp_ag *ag, uint8_t gain); + + /** HF ECNR turns off callback + * + * If this callback is provided it will be called whenever the + * ECNR turning off request is received from HF. + * If the callback is NULL or @kconfig{CONFIG_BT_HFP_AG_ECNR} + * is not enabled, the response result code of AT command + * will be an AT ERROR. + * + * @param ag HFP AG object. + */ + void (*ecnr_turn_off)(struct bt_hfp_ag *ag); + + /** HF explicit call transfer callback + * + * If this callback is provided it will be called whenever the + * AT+CHLD=4 is sent from HF. + * When the callback is notified, the application should connect + * the two calls and disconnects the subscriber from both calls + * (Explicit Call Transfer). + * After the callback returned, the call objects will be invalid. + * If the callback is NULL, the response result code of AT command + * will be an AT ERROR. + * If @kconfig{CONFIG_BT_HFP_AG_3WAY_CALL} is not enabled, the + * callback will not be notified. + * + * @param ag HFP AG object. + */ + void (*explicit_call_transfer)(struct bt_hfp_ag *ag); + + /** Voice recognition activation/deactivation callback + * + * If this callback is provided it will be called whenever the + * voice recognition activation is changed. + * If voice recognition is activated, the upper layer should + * call `bt_hfp_ag_audio_connect` with appropriate codec ID to + * setup audio connection. + * If the callback is not provided by upper layer, the function + * `bt_hfp_ag_audio_connect` will be called with default codec + * ID `BT_HFP_AG_CODEC_CVSD`. + * If @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG} is not enabled, + * the callback will not be notified. + * + * @param ag HFP AG object. + * @param activate Voice recognition activation/deactivation. + */ + void (*voice_recognition)(struct bt_hfp_ag *ag, bool activate); + + /** Ready to accept audio callback + * + * If this callback is provided it will be called whenever the + * HF is ready to accept audio. + * If the feature `Enhanced Voice Recognition Status` is supported + * by HF, the callback will be notified if the AT command `AT+BVRA=2` + * is received. The HF may send this value during an ongoing VR + * (Voice Recognition) session to terminate audio output from the + * AG (if there is any) and prepare the AG for new audio input. + * Or, the callback will be notified after the voice recognition + * is activated. + * If @kconfig{CONFIG_BT_HFP_AG_ENH_VOICE_RECG} is not enabled, + * the callback will not be notified. + * + * @param ag HFP AG object. + */ + void (*ready_to_accept_audio)(struct bt_hfp_ag *ag); + + /** Request phone number callback + * + * If this callback is provided it will be called whenever the + * AT command `AT+BINP=1` is received. + * If the upper layer accepts the request, it shall obtain a + * phone number. + * If the upper layer rejects the request, it shall return a + * an error. + * If @kconfig{CONFIG_BT_HFP_AG_VOICE_TAG} is not enabled, + * the callback will not be notified. + * + * @param ag HFP AG object. + * @param number Phone number of voice tag. + * + * @return 0 in case of success or negative value in case of error. + */ + int (*request_phone_number)(struct bt_hfp_ag *ag, char **number); + + /** Transmit a DTMF Code callback + * + * If this callback is provided it will be called whenever the + * AT command `AT+VTS=` is received. + * During an ongoing call, the HF transmits the AT+VTS command + * to instruct the AG to transmit a specific DTMF code to its + * network connection. + * + * @param ag HFP AG object. + * @param code A specific DTMF code. + */ + void (*transmit_dtmf_code)(struct bt_hfp_ag *ag, char code); + + /** Get subscriber number callback + * + * If this callback is provided it will be called whenever the + * AT command `AT+CNUM` is received. + * + * @param ag HFP AG object. + * @param func Query subscriber number callback. + * + * @return 0 in case of success or negative value in case of error. + */ + int (*subscriber_number)(struct bt_hfp_ag *ag, bt_hfp_ag_query_subscriber_func_t func); + + /** HF indicator value callback + * + * If this callback is provided it will be called whenever the + * AT command `AT+BIEV` is received. + * If @kconfig{CONFIG_BT_HFP_AG_HF_INDICATORS} is not enabled, + * the callback will not be notified. + * + * @param ag HFP AG object. + * @param indicator HF indicator + * @param value The value of specific indicator + */ + void (*hf_indicator_value)(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, + uint32_t value); }; /** @brief Register HFP AG profile @@ -200,48 +449,78 @@ int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag); * Notify HFP Unit of an incoming call. * * @param ag HFP AG object. - * @param number Dailing number. + * @param number Dialing number. * * @return 0 in case of success or negative value in case of error. */ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number); +/** @brief Put the incoming call on hold + * + * Put the incoming call on hold. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_hold_incoming(struct bt_hfp_ag_call *call); + /** @brief Reject the incoming call * * Reject the incoming call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_reject(struct bt_hfp_ag *ag); +int bt_hfp_ag_reject(struct bt_hfp_ag_call *call); /** @brief Accept the incoming call * * Accept the incoming call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_accept(struct bt_hfp_ag *ag); +int bt_hfp_ag_accept(struct bt_hfp_ag_call *call); /** @brief Terminate the active/hold call * * Terminate the active/hold call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_terminate(struct bt_hfp_ag *ag); +int bt_hfp_ag_terminate(struct bt_hfp_ag_call *call); + +/** @brief Retrieve the held call + * + * Retrieve the held call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_retrieve(struct bt_hfp_ag_call *call); + +/** @brief Hold the active call + * + * Hold the active call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_hold(struct bt_hfp_ag_call *call); /** @brief Dial a call * * Dial a call. * * @param ag HFP AG object. - * @param number Dailing number. + * @param number Dialing number. * * @return 0 in case of success or negative value in case of error. */ @@ -251,41 +530,266 @@ int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number); * * Notify HFP Unit that the remote starts ringing. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_remote_ringing(struct bt_hfp_ag *ag); +int bt_hfp_ag_remote_ringing(struct bt_hfp_ag_call *call); /** @brief Notify HFP Unit that the remote rejects the call * * Notify HFP Unit that the remote rejects the call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_remote_reject(struct bt_hfp_ag *ag); +int bt_hfp_ag_remote_reject(struct bt_hfp_ag_call *call); /** @brief Notify HFP Unit that the remote accepts the call * * Notify HFP Unit that the remote accepts the call. * - * @param ag HFP AG object. + * @param call HFP AG call object. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_remote_accept(struct bt_hfp_ag *ag); +int bt_hfp_ag_remote_accept(struct bt_hfp_ag_call *call); /** @brief Notify HFP Unit that the remote terminates the active/hold call * * Notify HFP Unit that the remote terminates the active/hold call. * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_remote_terminate(struct bt_hfp_ag_call *call); + +/** @brief explicit call transfer + * + * Connects the two calls and disconnects the subscriber from + * both calls (Explicit Call Transfer). + * If @kconfig{CONFIG_BT_HFP_AG_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_explicit_call_transfer(struct bt_hfp_ag *ag); + +/** @brief Set the HF microphone gain + * + * Set the HF microphone gain + * + * @param ag HFP AG object. + * @param vgm Microphone gain value. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm); + +/** @brief Set the HF speaker gain + * + * Set the HF speaker gain + * + * @param ag HFP AG object. + * @param vgs Speaker gain value. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs); + +/** @brief Set currently network operator + * + * Set currently network operator. + * + * @param ag HFP AG object. + * @param mode Current mode and provides no information with regard + * to the name of the operator. + * @param name A string in alphanumeric format representing the + * name of the network operator. This string shall + * not exceed 16 characters. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name); + +/** @brief Create audio connection + * + * Create audio conenction by HFP AG. There are two setups included, + * Codec connection and audio connection. + * The codec connection will be established firstly if the codec + * negotiation are supported by both side. If the passed codec id + * is not same as the last codec connection, the codec connection + * procedure will be triggered. + * After the codec conenction is established, the audio conenction + * will be started. + * The passed codec id could be one of BT_HFP_AG_CODEC_XXX. If the + * codec negotiation feature is supported by both side, the codec id + * could be one of the bitmaps of `ids` notified by callback `codec`. + * Or, the `id` should be BT_HFP_AG_CODEC_CVSD. + * + * @param ag HFP AG object. + * @param id Codec Id. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id); + +/** @brief Set In-Band Ring Tone + * + * Set In-Band Ring Tone. + * + * @param ag HFP AG object. + * @param inband In-band or no in-band. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband); + +/** @brief Enable/disable the voice recognition function + * + * Enables/disables the voice recognition function. + * If @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param activate Activate/deactivate the voice recognition function. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate); + +/** @brief set voice recognition engine state + * + * It is used to set the voice recognition engine state. + * The unsolicited result code `+BVRA: 1,` will be sent. + * ``: Bitmask that reflects the current state of the voice + * recognition engine on the AG. + * Bit 0 - If it is 1, the AG is ready to accept audio input + * Bit 1 - If it is 1, the AG is sending audio to the HF + * Bit 2 - If it is 1, the AG is processing the audio input + * If @kconfig{CONFIG_BT_HFP_AG_ENH_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param state The value of ``. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state); + +/** @brief set voice recognition engine state and textual representation + * + * It is used to set the voice recognition engine state with + * textual representation. + * unsolicited result code `+BVRA: 1,, + * ` will be sent. + * `` is same as parameter `state` of function + * `bt_hfp_ag_vre_state`. + * `: ,,, + * `. + * ``: Unique ID of the current text as a hexadecimal string + * (a maximum of 4 characters in length, but less than 4 characters + * in length is valid). + * ``: ID of the textType from the following list: + * 0 - Text recognized by the AG from the audio input provided by the HF + * 1 - Text of the audio output from the AG + * 2 - Text of the audio output from the AG that contains a question + * 3 - Text of the audio output from the AG that contains an error + * description + * ``: ID of the operation of the text + * 1 - NewText: Indicates that a new text started. Shall be used when the + * `` changes + * 2 - Replace: Replace any existing text with the same `` and + * same `` + * 3 - Append: Attach new text to existing text and keep the same + * `` and same `` + * ``: The `` parameter shall be a UTF-8 text string and + * shall always be contained within double quotes. + * If @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG_TEXT} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param state The value of ``. + * @param id Value of ``. + * @param type Value of ``. + * @param operation Value of ``. + * @param text Value of ``. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, const char *id, + uint8_t type, uint8_t operation, const char *text); + +/** @brief Set signal strength + * + * Set signal strength. + * + * @param ag HFP AG object. + * @param strength Signal strength. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength); + +/** @brief Set roaming status + * + * Set roaming status. + * + * @param ag HFP AG object. + * @param status Roaming status. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status); + +/** @brief Set battery level + * + * Set battery level. + * + * @param ag HFP AG object. + * @param level battery level. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level); + +/** @brief Set service availability + * + * Set service availability. + * + * @param ag HFP AG object. + * @param available service availability + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available); + +/** @brief Activate/deactivate HF indicator + * + * It allows HF to issue the +BIND unsolicited result code to + * activate/deactivate of the AG’s supported HF Indicators. + * The indicator of supported indicators can be activated/deactivated + * are defined in `enum hfp_ag_hf_indicators`. + * `BT_HFP_AG_HF_INDICATOR_ENH_SAFETY` is used to support + * `Enhanced Safety`. Only the configuration has been enabled, the + * `indicator` can be HFP_AG_ENHANCED_SAFETY_IND. + * `BT_HFP_AG_HF_INDICATOR_BATTERY` is used to support + * `Remaining level of Battery`. Only the configuration has been + * enabled, the `indicator` can be HFP_AG_BATTERY_LEVEL_IND. + * If @kconfig{CONFIG_BT_HFP_HF_HF_INDICATORS} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * * @param ag HFP AG object. + * @param indicator The indicator of the AG’s supported HF Indicators. + * @param enable enable/disable specific HF Indicator. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_ag_remote_terminate(struct bt_hfp_ag *ag); +int bt_hfp_ag_hf_indicator(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, bool enable); #ifdef __cplusplus } diff --git a/include/zephyr/bluetooth/classic/hfp_hf.h b/include/zephyr/bluetooth/classic/hfp_hf.h index ded881351ca29..3a6dd4fc8f59f 100644 --- a/include/zephyr/bluetooth/classic/hfp_hf.h +++ b/include/zephyr/bluetooth/classic/hfp_hf.h @@ -23,27 +23,14 @@ extern "C" { #endif -/* AT Commands */ -enum bt_hfp_hf_at_cmd { - BT_HFP_HF_ATA, - BT_HFP_HF_AT_CHUP, -}; +/* HFP CODEC IDs */ +#define BT_HFP_HF_CODEC_CVSD 0x01 +#define BT_HFP_HF_CODEC_MSBC 0x02 +#define BT_HFP_HF_CODEC_LC3_SWB 0x03 -/* - * Command complete types for the application - */ -#define HFP_HF_CMD_OK 0 -#define HFP_HF_CMD_ERROR 1 -#define HFP_HF_CMD_CME_ERROR 2 -#define HFP_HF_CMD_UNKNOWN_ERROR 4 - -/** @brief HFP HF Command completion field */ -struct bt_hfp_hf_cmd_complete { - /* Command complete status */ - uint8_t type; - /* CME error number to be added */ - uint8_t cme; -}; +struct bt_hfp_hf; + +struct bt_hfp_hf_call; /** @brief HFP profile application callback */ struct bt_hfp_hf_cb { @@ -53,32 +40,35 @@ struct bt_hfp_hf_cb { * connection completes. * * @param conn Connection object. + * @param hf HFP HF object. */ - void (*connected)(struct bt_conn *conn); + void (*connected)(struct bt_conn *conn, struct bt_hfp_hf *hf); /** HF disconnected callback to application * * If this callback is provided it will be called whenever the * connection gets disconnected, including when a connection gets * rejected or cancelled or any error in SLC establishment. + * And the HFP HF object will be freed after the registered + * callback `disconnected` returned. * - * @param conn Connection object. + * @param hf HFP HF object. */ - void (*disconnected)(struct bt_conn *conn); + void (*disconnected)(struct bt_hfp_hf *hf); /** HF SCO/eSCO connected Callback * * If this callback is provided it will be called whenever the * SCO/eSCO connection completes. * - * @param conn Connection object. + * @param hf HFP HF object. * @param sco_conn SCO/eSCO Connection object. */ - void (*sco_connected)(struct bt_conn *conn, struct bt_conn *sco_conn); + void (*sco_connected)(struct bt_hfp_hf *hf, struct bt_conn *sco_conn); /** HF SCO/eSCO disconnected Callback * * If this callback is provided it will be called whenever the * SCO/eSCO connection gets disconnected. * - * @param conn Connection object. + * @param conn SCO/eSCO Connection object. * @param reason BT_HCI_ERR_* reason for the disconnection. */ void (*sco_disconnected)(struct bt_conn *sco_conn, uint8_t reason); @@ -86,75 +76,346 @@ struct bt_hfp_hf_cb { * * This callback provides service indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value service indicator value received from the AG. */ - void (*service)(struct bt_conn *conn, uint32_t value); - /** HF indicator Callback + void (*service)(struct bt_hfp_hf *hf, uint32_t value); + /** HF call outgoing Callback * - * This callback provides call indicator value to the application + * This callback provides the outgoing call status to + * the application. * - * @param conn Connection object. - * @param value call indicator value received from the AG. + * @param hf HFP HF object. + * @param call HFP HF call object. */ - void (*call)(struct bt_conn *conn, uint32_t value); - /** HF indicator Callback + void (*outgoing)(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call); + /** HF call outgoing call is ringing Callback * - * This callback provides call setup indicator value to the application + * This callback provides the outgoing call is ringing + * status to the application. * - * @param conn Connection object. - * @param value call setup indicator value received from the AG. + * @param call HFP HF call object. */ - void (*call_setup)(struct bt_conn *conn, uint32_t value); - /** HF indicator Callback + void (*remote_ringing)(struct bt_hfp_hf_call *call); + /** HF call incoming Callback * - * This callback provides call held indicator value to the application + * This callback provides the incoming call status to + * the application. * - * @param conn Connection object. - * @param value call held indicator value received from the AG. + * @param hf HFP HF object. + * @param call HFP HF call object. + */ + void (*incoming)(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call); + /** HF incoming call on hold Callback + * + * This callback provides the incoming call on hold status to + * the application. + * + * @param call HFP HF call object. + */ + void (*incoming_held)(struct bt_hfp_hf_call *call); + /** HF call accept Callback + * + * This callback provides the incoming/outgoing call active + * status to the application. + * + * @param call HFP HF call object. */ - void (*call_held)(struct bt_conn *conn, uint32_t value); + void (*accept)(struct bt_hfp_hf_call *call); + /** HF call reject Callback + * + * This callback provides the incoming/outgoing call reject + * status to the application. + * + * @param call HFP HF call object. + */ + void (*reject)(struct bt_hfp_hf_call *call); + /** HF call terminate Callback + * + * This callback provides the incoming/outgoing call terminate + * status to the application. + * + * @param call HFP HF call object. + */ + void (*terminate)(struct bt_hfp_hf_call *call); + /** HF call held Callback + * + * This callback provides call held to the application + * + * @param call HFP HF call object. + */ + void (*held)(struct bt_hfp_hf_call *call); + /** HF call retrieve Callback + * + * This callback provides call retrieved to the application + * + * @param call HFP HF call object. + */ + void (*retrieve)(struct bt_hfp_hf_call *call); /** HF indicator Callback * * This callback provides signal indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value signal indicator value received from the AG. */ - void (*signal)(struct bt_conn *conn, uint32_t value); + void (*signal)(struct bt_hfp_hf *hf, uint32_t value); /** HF indicator Callback * * This callback provides roaming indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value roaming indicator value received from the AG. */ - void (*roam)(struct bt_conn *conn, uint32_t value); + void (*roam)(struct bt_hfp_hf *hf, uint32_t value); /** HF indicator Callback * * This callback battery service indicator value to the application * - * @param conn Connection object. + * @param hf HFP HF object. * @param value battery indicator value received from the AG. */ - void (*battery)(struct bt_conn *conn, uint32_t value); + void (*battery)(struct bt_hfp_hf *hf, uint32_t value); /** HF incoming call Ring indication callback to application * * If this callback is provided it will be called whenever there * is an incoming call. * - * @param conn Connection object. + * @param call HFP HF call object. */ - void (*ring_indication)(struct bt_conn *conn); - /** HF notify command completed callback to application + void (*ring_indication)(struct bt_hfp_hf_call *call); + /** HF call dialing Callback * - * The command sent from the application is notified about its status + * This callback provides call dialing result to the application. * - * @param conn Connection object. - * @param cmd structure contains status of the command including cme. + * @param hf HFP HF object. + * @param err Result of calling dialing. + */ + void (*dialing)(struct bt_hfp_hf *hf, int err); + /** HF calling line identification notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +CLIP. + * If @kconfig{CONFIG_BT_HFP_HF_CLI} is not enabled, the unsolicited + * result code +CLIP will be ignored. And the callback will not be + * notified. + * + * @param call HFP HF call object. + * @param number Notified phone number. + * @param type Specify the format of the phone number. + */ + void (*clip)(struct bt_hfp_hf_call *call, char *number, uint8_t type); + /** HF microphone gain notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +VGM. + * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the unsolicited + * result code +VGM will be ignored. And the callback will not be + * notified. + * + * @param hf HFP HF object. + * @param gain Microphone gain. + */ + void (*vgm)(struct bt_hfp_hf *hf, uint8_t gain); + /** HF speaker gain notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +VGS. + * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the unsolicited + * result code +VGS will be ignored. And the callback will not be + * notified. + * + * @param hf HFP HF object. + * @param gain Speaker gain. + */ + void (*vgs)(struct bt_hfp_hf *hf, uint8_t gain); + /** HF in-band ring tone notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +BSIR issued by the AG to + * indicate to the HF that the in-band ring tone setting + * has been locally changed. + * + * @param hf HFP HF object. + * @param inband In-band ring tone status from the AG. + */ + void (*inband_ring)(struct bt_hfp_hf *hf, bool inband); + /** HF network operator notification callback to application + * + * If this callback is provided it will be called whenever there + * is a response code +COPS issued by the AG to + * response the AT+COPS? command issued by the HF by calling + * function `bt_hfp_hf_get_operator`. + * + * @param hf HFP HF object. + * @param mode Current mode. + * @param format Format of the `operator` parameter string. + * It should be zero. + * @param operator A string in alphanumeric format + * representing the name of the network + * operator. + */ + void (*operator)(struct bt_hfp_hf *hf, uint8_t mode, uint8_t format, char *operator); + /** Codec negotiate callback + * + * If this callback is provided it will be called whenever the + * unsolicited codec negotiation response received. + * There are two cases when the callback triggered, + * Case 1, the codec id can be accepted, the function + * `bt_hfp_hf_select_codec` should be called to accept the codec + * id. + * Case 2, the codec id can not be accepted, the function + * `bt_hfp_hf_set_codecs` should be called to trigger codec ID + * to be re-selected. + * If the callback is not provided by application, the function + * `bt_hfp_hf_select_codec` will be called to accept the codec + * id. + * Refers to BT_HFP_HF_CODEC_XXX for codec id value. + * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the + * unsolicited result code +BCS will be ignored. And the callback + * will not be notified. + * + * @param hf HFP HF object. + * @param id Negotiated Codec ID. + */ + void (*codec_negotiate)(struct bt_hfp_hf *hf, uint8_t id); + /** HF ECNR turns off callback + * + * If this callback is provided it will be called whenever the + * response of ECNR turning off is received from AG. + * If @kconfig{CONFIG_BT_HFP_HF_ECNR} is not enabled, the + * callback will not be notified. + * + * @param hf HFP HF object. + * @param err The result of request. + */ + void (*ecnr_turn_off)(struct bt_hfp_hf *hf, int err); + /** HF call waiting notification callback to application + * + * If this callback is provided it will be called whenever there + * is a unsolicited result code +CCWA. + * This notification can be enabled/disabled by calling function + * `bt_hfp_hf_call_waiting_notify`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the + * unsolicited result code +CCWA will be ignored. And the callback + * will not be notified. + * + * @param call HFP HF call object. + * @param number Notified phone number. + * @param type Specify the format of the phone number. + */ + void (*call_waiting)(struct bt_hfp_hf_call *call, char *number, uint8_t type); + /** Voice recognition activation/deactivation callback + * + * If this callback is provided it will be called whenever the + * unsolicited result code +BVRA is notified the HF when the + * voice recognition function in the AG is activated/deactivated + * autonomously from the AG. + * If @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback + * will not be notified. + * + * @param hf HFP HF object. + * @param activate Voice recognition activation/deactivation. + */ + void (*voice_recognition)(struct bt_hfp_hf *hf, bool activate); + /** Voice recognition engine state callback + * + * If this callback is provided it will be called whenever the + * unsolicited result code `+BVRA: 1,` is received from AG. + * ``: Bitmask that reflects the current state of the voice + * recognition engine on the AG. + * Bit 0 - If it is 1, the AG is ready to accept audio input + * Bit 1 - If it is 1, the AG is sending audio to the HF + * Bit 2 - If it is 1, the AG is processing the audio input + * If @kconfig{CONFIG_BT_HFP_HF_ENH_VOICE_RECG} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback + * will not be notified. + * + * @param hf HFP HF object. + * @param state Value of ``. */ - void (*cmd_complete_cb)(struct bt_conn *conn, - struct bt_hfp_hf_cmd_complete *cmd); + void (*vre_state)(struct bt_hfp_hf *hf, uint8_t state); + /** Textual representation callback + * + * If this callback is provided it will be called whenever the + * unsolicited result code `+BVRA: 1,, + * ` is received from AG. + * `: ,,, + * `. + * ``: Unique ID of the current text as a hexadecimal string + * (a maximum of 4 characters in length, but less than 4 characters + * in length is valid). + * ``: ID of the textType from the following list: + * 0 - Text recognized by the AG from the audio input provided by the HF + * 1 - Text of the audio output from the AG + * 2 - Text of the audio output from the AG that contains a question + * 3 - Text of the audio output from the AG that contains an error + * description + * ``: ID of the operation of the text + * 1 - NewText: Indicates that a new text started. Shall be used when the + * `` changes + * 2 - Replace: Replace any existing text with the same `` and + * same `` + * 3 - Append: Attach new text to existing text and keep the same + * `` and same `` + * ``: The `` parameter shall be a UTF-8 text string and + * shall always be contained within double quotes. + * If @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG_TEXT} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback + * will not be notified. + * + * @param hf HFP HF object. + * @param id Value of ``. + * @param type Value of ``. + * @param operation Value of ``. + * @param text Value of ``. + */ + void (*textual_representation)(struct bt_hfp_hf *hf, char *id, uint8_t type, + uint8_t operation, char *text); + /** Request phone number callback + * + * If this callback is provided it will be called whenever the + * result code `+BINP: ` is received from AG. + * If the request is failed, the `number` will be NULL. + * + * @param hf HFP HF object. + * @param number Value of ``. + */ + void (*request_phone_number)(struct bt_hfp_hf *hf, const char *number); + + /** Query subscriber number callback + * + * If this callback is provided it will be called whenever the + * result code `+CUNM: [],, ,[ ,]` + * is received from AG. + * ``: This optional field is not supported, and shall be left + * blank. + * ``: Quoted string containing the phone number in the format + * specified by ``. + * `` field specifies the format of the phone number provided, + * and can be one of the following values: + * - values 128-143: The phone number format may be a national or + * international format, and may contain prefix and/or escape digits. + * No changes on the number presentation are required. + * - values 144-159: The phone number format is an international + * number, including the country code prefix. If the plus sign ("+") + * is not included as part of the number and shall be added by the AG + * as needed. + * - values 160-175: National number. No prefix nor escape digits + * included. + * ``: This optional field is not supported, and shall be left + * blank. + * ``: Indicates which service this phone number relates to. + * Shall be either 4 (voice) or 5 (fax). + * + * @param hf HFP HF object. + * @param number Value of `` without quotes. + * @param type Value of ``. + * @param service Value of ``. + */ + void (*subscriber_number)(struct bt_hfp_hf *hf, const char *number, uint8_t type, + uint8_t service); }; /** @brief Register HFP HF profile @@ -168,16 +429,528 @@ struct bt_hfp_hf_cb { */ int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb); -/** @brief Handsfree client Send AT +/** @brief Initiate the service level connection establishment procedure + * + * Initiate the service level connection establishment procedure on the + * ACL connection specified by the parameter `conn` using the specific + * RFCOMM channel discovered by the function `bt_br_discovery_start`. + * + * The parameter `hf` is a output parameter. When the service level + * connection establishment procedure is initiated without any error, + * the HFP HF object is allocated and it will be returned via the parameter + * `hf` if the parameter `hf` is not a NULL pointer. + * + * When service level conenction is established, the registered callback + * `connected` will be triggered to notify the application that the service + * level connection establishment procedure is done. And the HFP HF object + * is valid at this time. It means after the function is called without + * any error, all interfaces provided by HFP HF can only be called after + * the registered callback `connected` is triggered. + * + * @param conn ACL connection object. + * @param hf Created HFP HF object. + * @param channel Peer RFCOMM channel to be connected. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_connect(struct bt_conn *conn, struct bt_hfp_hf **hf, uint8_t channel); + +/** @brief Release the service level connection + * + * Release the service level connection from the peer device. + * + * The function can only be called after the registered callback `connected` + * is triggered. + * + * If the function is called without any error, the HFP HF object is + * invalid at this time. All interfaces provided by HFP HF should not + * be called anymore. + * + * If the service level connection is released, the registered callback + * `disconnected` will be triggered to notify the application that the + * service level connection release procedure is done. And the HFP HF + * object will be freed after the registered callback `disconnected` + * returned. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_disconnect(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF enable/disable Calling Line Identification (CLI) Notification + * + * Enable/disable Calling Line Identification (CLI) Notification. + * The AT command `AT+CLIP` will be sent to the AG to enable/disable the CLI + * unsolicited result code +CLIP when calling the function. + * If @kconfig{CONFIG_BT_HFP_HF_CLI} is not enabled, the error `-ENOTSUP` will + * be returned if the function called. + * + * @param hf HFP HF object. + * @param enable Enable/disable CLI. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable); + +/** @brief Handsfree HF report Gain of Microphone (VGM) + * + * Report Gain of Microphone (VGM). + * The AT command `AT+VGM=` will be sent to the AG to report its + * current microphone gain level setting to the AG. + * `` is a decimal numeric constant, relating to a particular + * (implementation dependent) volume level controlled by the HF. + * This command does not change the microphone gain of the AG; it simply + * indicates the current value of the microphone gain in the HF. + * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * For "Volume Level Synchronization", the HF application could call + * the function to set VGM gain value in HF connection callback + * function. Then after the HF connection callback returned, VGM gain + * will be sent to HFP AG. + * + * @param hf HFP HF object. + * @param gain Gain of microphone. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain); + +/** @brief Handsfree HF report Gain of Speaker (VGS) + * + * Report Gain of Speaker (VGS). + * The AT command `AT+VGS=` will be sent to the AG to report its + * current speaker gain level setting to the AG. + * `` is a decimal numeric constant, relating to a particular + * (implementation dependent) volume level controlled by the HF. + * This command does not change the speaker gain of the AG; it simply + * indicates the current value of the speaker gain in the HF. + * If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * For "Volume Level Synchronization", the HF application could call + * the function to set VGS gain value in HF connection callback + * function. Then after the HF connection callback returned, VGS gain + * will be sent to HFP AG. + * + * @param hf HFP HF object. + * @param gain Gain of speaker. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain); + +/** @brief Handsfree HF requests currently selected operator + * + * Send the AT+COPS? (Read) command to find the currently + * selected operator. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF accept the incoming call + * + * Send the ATA command to accept the incoming call. + * OR, send the AT+BTRH=1 command to accept a held incoming + * call. + * + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_accept(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF reject the incoming call + * + * Send the AT+CHUP command to reject the incoming call. + * OR, send the AT+BTRH=2 command to reject a held incoming + * call. + * + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_reject(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF terminate the incoming call + * + * Send the AT+CHUP command to terminate the incoming call. + * + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_terminate(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF put the incoming call on hold + * + * Send the AT+BTRH=0 command to put the incoming call on hold. + * If the incoming call has been held, the callback `on_hold` will + * be triggered. + * + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_hold_incoming(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF query respond and hold status of AG + * + * Send the AT+BTRH? command to query respond and hold status of AG. + * The status respond and hold will be notified through callback + * `on_hold`. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF phone number call + * + * Initiate outgoing voice calls by providing the destination phone + * number to the AG. + * Send the ATDdd…dd command to start phone number call. + * The result of the command will be notified through the callback + * `dialing`. + * + * @param hf HFP HF object. + * @param number Phone number. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number); + +/** @brief Handsfree HF memory dialing call + * + * Initiate outgoing voice calls using the memory dialing feature + * of the AG. + * Send the ATD>Nan... command to start memory dialing. + * The result of the command will be notified through the callback + * `dialing`. + * + * @param hf HFP HF object. + * @param location Memory location. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location); + +/** @brief Handsfree HF redial last number + * + * Initiate outgoing voice calls by recalling the last number + * dialed by the AG. + * Send the AT+BLDN command to recall the last number. + * The result of the command will be notified through the callback + * `dialing`. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_redial(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF setup audio connection + * + * Setup audio conenction by sending AT+BCC. + * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF set selected codec id + * + * Set selected codec id by sending AT+BCS. The function is used to + * response the codec negotiation request notified by callback + * `codec_negotiate`. The parameter `codec_id` should be same as + * `id` of callback `codec_negotiate` if the id could be supported. + * Or, call `bt_hfp_hf_set_codecs` to notify the AG Codec IDs supported + * by HFP HF. + * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param codec_id Selected codec id. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id); + +/** @brief Handsfree HF set supported codec ids + * + * Set supported codec ids by sending AT+BAC. This function is used + * to notify AG the supported Codec IDs of HF. + * If @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param codec_ids Supported codec IDs. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids); + +/** @brief Handsfree HF turns off AG's EC and NR + * + * Turn off the AG's EC and NR by sending `AT+NREC=0`. + * The result of the command is notified through the callback + * `ecnr_turn_off`. + * If @kconfig{CONFIG_BT_HFP_HF_ECNR} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF enable/disable call waiting notification + * + * Enable call waiting notification by sending `AT+CCWA=1`. + * Disable call waiting notification by sending `AT+CCWA=0`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param enable Enable/disable. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_call_waiting_notify(struct bt_hfp_hf *hf, bool enable); + +/** @brief Handsfree HF release all held calls + * + * Release all held calls by sending `AT+CHLD=0`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_release_all_held(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF set User Determined User Busy (UDUB) for a waiting call + * + * Set User Determined User Busy (UDUB) for a waiting call + * by sending `AT+CHLD=0`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_set_udub(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF release all active calls and accept other call + * + * Release all active calls (if any exist) and accepts the other + * (held or waiting) call by sending `AT+CHLD=1`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_release_active_accept_other(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF hold all active calls and accept other call + * + * Hold all active calls (if any exist) and accepts the other + * (held or waiting) call by sending `AT+CHLD=2`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_hold_active_accept_other(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF add a held call to the conversation + * + * Add a held call to the conversation by sending `AT+CHLD=3`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_join_conversation(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF explicit call transfer + * + * Connects the two calls and disconnects the subscriber from + * both calls (Explicit Call Transfer) by sending `AT+CHLD=4`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_explicit_call_transfer(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF release call with specified index + * + * Release call with specified index by sending `AT+CHLD=1`. + * `` is index of specified call. + * If @kconfig{CONFIG_BT_HFP_HF_ECC} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_release_specified_call(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF request private consultation mode with specified call + * + * Request private consultation mode with specified call (Place all calls + * on hold EXCEPT the call indicated by ``.) by sending + * `AT+CHLD=2`. + * `` is index of specified call. + * If @kconfig{CONFIG_BT_HFP_HF_ECC} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF enable/disable the voice recognition function + * + * Enables/disables the voice recognition function in the AG. + * If @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param activate Activate/deactivate the voice recognition function. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_voice_recognition(struct bt_hfp_hf *hf, bool activate); + +/** @brief Handsfree HF indicate that the HF is ready to accept audio + * + * This value indicates that the HF is ready to accept audio when + * the Audio Connection is first established. The HF shall only send + * this value if the eSCO link has been established. + * If @kconfig{CONFIG_BT_HFP_HF_ENH_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_ready_to_accept_audio(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF attach a phone number for a voice tag + * + * Send AT command "AT+BINP=1" to request phone number to the AG. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF Transmit A specific DTMF Code + * + * During an ongoing call, the HF transmits the AT+VTS command to + * instruct the AG to transmit a specific DTMF code to its network + * connection. + * The set of the code is "0-9,#,*,A-D". + * + * @param call HFP HF call object. + * @param code A specific DTMF code. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code); + +/** @brief Handsfree HF Query Subscriber Number Information + * + * It allows HF to query the AG subscriber number by sending `AT+CNUM`. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_query_subscriber(struct bt_hfp_hf *hf); + +/* HFP HF Indicators */ +enum hfp_hf_ag_indicators { + HF_SERVICE_IND = 0, /* AG service indicator */ + HF_CALL_IND, /* AG call indicator */ + HF_CALL_SETUP_IND, /* AG call setup indicator */ + HF_CALL_HELD_IND, /* AG call held indicator */ + HF_SIGNAL_IND, /* AG signal indicator */ + HF_ROAM_IND, /* AG roaming indicator */ + HF_BATTERY_IND /* AG battery indicator */ +}; + +/** @brief Handsfree HF set AG indicator activated/deactivated status + * + * It allows HF to issue the AT+BIA command if it needs to change the + * activated/deactivated status of indicators in the AG. + * The index of all indicators can be activated/deactivated are + * defined in `enum hfp_hf_ag_indicators`. + * The each bit of parameter `status` represents the indicator status + * corresponding to the index. Such as, value 0b111110 of `status` + * means the AG indicator `service` is required to be deactivated. + * Others are required to be activated. + * + * @param hf HFP HF object. + * @param status The activated/deactivated bitmap status of AG indicators. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_indicator_status(struct bt_hfp_hf *hf, uint8_t status); + +/** @brief Handsfree HF enable/disable enhanced safety + * + * It allows HF to transfer of HF indicator enhanced safety value. + * If @kconfig{CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY} is not enabled, + * the error `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param enable The enhanced safety is enabled/disabled. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_hfp_hf_enhanced_safety(struct bt_hfp_hf *hf, bool enable); + +/** @brief Handsfree HF remaining battery level * - * Send specific AT commands to handsfree client profile. + * It allows HF to transfer of HF indicator remaining battery level value. + * If @kconfig{CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY} is not enabled, + * the error `-ENOTSUP` will be returned if the function called. * - * @param conn Connection object. - * @param cmd AT command to be sent. + * @param hf HFP HF object. + * @param level The remaining battery level. * * @return 0 in case of success or negative value in case of error. */ -int bt_hfp_hf_send_cmd(struct bt_conn *conn, enum bt_hfp_hf_at_cmd cmd); +int bt_hfp_hf_battery(struct bt_hfp_hf *hf, uint8_t level); #ifdef __cplusplus } diff --git a/samples/bluetooth/handsfree/src/main.c b/samples/bluetooth/handsfree/src/main.c index cbad46596a69b..140eed4a350da 100644 --- a/samples/bluetooth/handsfree/src/main.c +++ b/samples/bluetooth/handsfree/src/main.c @@ -19,67 +19,103 @@ #include #include -static void connected(struct bt_conn *conn) +static void hf_connected(struct bt_conn *conn, struct bt_hfp_hf *hf) { printk("HFP HF Connected!\n"); } -static void disconnected(struct bt_conn *conn) +static void hf_disconnected(struct bt_hfp_hf *hf) { printk("HFP HF Disconnected!\n"); } -static void service(struct bt_conn *conn, uint32_t value) +static void hf_sco_connected(struct bt_hfp_hf *hf, struct bt_conn *sco_conn) +{ + printk("HF SCO connected\n"); +} + +static void hf_sco_disconnected(struct bt_conn *sco_conn, uint8_t reason) +{ + printk("HF SCO disconnected\n"); +} + +static void hf_service(struct bt_hfp_hf *hf, uint32_t value) { printk("Service indicator value: %u\n", value); } -static void call(struct bt_conn *conn, uint32_t value) +static void hf_outgoing(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call) +{ + printk("HF call %p outgoing\n", call); +} + +static void hf_remote_ringing(struct bt_hfp_hf_call *call) +{ + printk("HF remote call %p start ringing\n", call); +} + +static void hf_incoming(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call) +{ + printk("HF call %p incoming\n", call); +} + +static void hf_incoming_held(struct bt_hfp_hf_call *call) +{ + printk("HF call %p is held\n", call); +} + +static void hf_accept(struct bt_hfp_hf_call *call) { - printk("Call indicator value: %u\n", value); + printk("HF call %p accepted\n", call); } -static void call_setup(struct bt_conn *conn, uint32_t value) +static void hf_reject(struct bt_hfp_hf_call *call) { - printk("Call Setup indicator value: %u\n", value); + printk("HF call %p rejected\n", call); } -static void call_held(struct bt_conn *conn, uint32_t value) +static void hf_terminate(struct bt_hfp_hf_call *call) { - printk("Call Held indicator value: %u\n", value); + printk("HF call %p terminated\n", call); } -static void signal(struct bt_conn *conn, uint32_t value) +static void hf_signal(struct bt_hfp_hf *hf, uint32_t value) { printk("Signal indicator value: %u\n", value); } -static void roam(struct bt_conn *conn, uint32_t value) +static void hf_roam(struct bt_hfp_hf *hf, uint32_t value) { printk("Roaming indicator value: %u\n", value); } -static void battery(struct bt_conn *conn, uint32_t value) +static void hf_battery(struct bt_hfp_hf *hf, uint32_t value) { printk("Battery indicator value: %u\n", value); } -static void ring_cb(struct bt_conn *conn) +static void hf_ring_indication(struct bt_hfp_hf_call *call) { - printk("Incoming Call...\n"); + printk("HF call %p ring\n", call); } static struct bt_hfp_hf_cb hf_cb = { - .connected = connected, - .disconnected = disconnected, - .service = service, - .call = call, - .call_setup = call_setup, - .call_held = call_held, - .signal = signal, - .roam = roam, - .battery = battery, - .ring_indication = ring_cb, + .connected = hf_connected, + .disconnected = hf_disconnected, + .sco_connected = hf_sco_connected, + .sco_disconnected = hf_sco_disconnected, + .service = hf_service, + .outgoing = hf_outgoing, + .remote_ringing = hf_remote_ringing, + .incoming = hf_incoming, + .incoming_held = hf_incoming_held, + .accept = hf_accept, + .reject = hf_reject, + .terminate = hf_terminate, + .signal = hf_signal, + .roam = hf_roam, + .battery = hf_battery, + .ring_indication = hf_ring_indication, }; static void bt_ready(int err) diff --git a/samples/bluetooth/handsfree_ag/src/main.c b/samples/bluetooth/handsfree_ag/src/main.c index a87608c6eaafb..721b8f6e26057 100644 --- a/samples/bluetooth/handsfree_ag/src/main.c +++ b/samples/bluetooth/handsfree_ag/src/main.c @@ -25,8 +25,7 @@ static struct bt_conn *default_conn; struct bt_hfp_ag *hfp_ag; - -static struct bt_br_discovery_param br_discover; +struct bt_hfp_ag_call *hfp_ag_call; static struct bt_br_discovery_param br_discover; static struct bt_br_discovery_result scan_result[CONFIG_BT_HFP_AG_DISCOVER_RESULT_COUNT]; @@ -41,8 +40,15 @@ struct k_work_delayable call_remote_accept_work; NET_BUF_POOL_DEFINE(sdp_discover_pool, 10, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); -static void ag_connected(struct bt_hfp_ag *ag) +static void ag_connected(struct bt_conn *conn, struct bt_hfp_ag *ag) { + if (conn != default_conn) { + printk("The conn %p is not aligned with ACL conn %p", conn, default_conn); + } + + if (!hfp_ag) { + hfp_ag = ag; + } printk("HFP AG connected!\n"); k_work_schedule(&call_connect_work, K_MSEC(CONFIG_BT_HFP_AG_START_CALL_DELAY_TIME)); } @@ -62,38 +68,40 @@ static void ag_sco_disconnected(struct bt_hfp_ag *ag) printk("HFP AG SCO disconnected!\n"); } -static void ag_ringing(struct bt_hfp_ag *ag, bool in_band) +static void ag_ringing(struct bt_hfp_ag_call *call, bool in_band) { printk("Ringing (in bond? %s)\n", in_band ? "Yes" : "No"); } -static void ag_accept(struct bt_hfp_ag *ag) +static void ag_accept(struct bt_hfp_ag_call *call) { printk("Call Accepted\n"); k_work_schedule(&call_disconnect_work, K_SECONDS(10)); } -static void ag_reject(struct bt_hfp_ag *ag) +static void ag_reject(struct bt_hfp_ag_call *call) { printk("Call Rejected\n"); k_work_schedule(&call_disconnect_work, K_SECONDS(1)); } -static void ag_terminate(struct bt_hfp_ag *ag) +static void ag_terminate(struct bt_hfp_ag_call *call) { printk("Call terminated\n"); k_work_schedule(&call_disconnect_work, K_SECONDS(1)); } -static void ag_outgoing(struct bt_hfp_ag *ag, const char *number) +static void ag_outgoing(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number) { + hfp_ag_call = call; printk("Call outgoing, remote number %s\n", number); k_work_cancel_delayable(&call_connect_work); k_work_schedule(&call_remote_ringing_work, K_SECONDS(1)); } -static void ag_incoming(struct bt_hfp_ag *ag, const char *number) +static void ag_incoming(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number) { + hfp_ag_call = call; printk("Incoming call, remote number %s\n", number); k_work_cancel_delayable(&call_connect_work); } @@ -329,7 +337,7 @@ static void call_remote_ringing_work_handler(struct k_work *work) printk("Remote starts ringing\n"); - err = bt_hfp_ag_remote_ringing(hfp_ag); + err = bt_hfp_ag_remote_ringing(hfp_ag_call); if (err != 0) { printk("Fail to notify hfp unit that the remote starts ringing (err %d)\n", err); @@ -344,7 +352,7 @@ static void call_remote_accept_work_handler(struct k_work *work) printk("Remote accepts the call\n"); - err = bt_hfp_ag_remote_accept(hfp_ag); + err = bt_hfp_ag_remote_accept(hfp_ag_call); if (err != 0) { printk("Fail to notify hfp unit that the remote accepts call (err %d)\n", err); diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 9a4fbc47ef03e..1fbf45f9099c6 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -174,7 +174,144 @@ config BT_HFP_AG help This option enables Bluetooth AG support +if BT_HFP_HF +config BT_HFP_HF_CLI + bool "CLI presentation capability for HFP HF [EXPERIMENTAL]" + default y + help + This option enables CLI presentation capability for HFP HF + +config BT_HFP_HF_VOLUME + bool "Remote audio volume control for HFP HF [EXPERIMENTAL]" + default y + help + This option enables Remote audio volume control for HFP HF + +config BT_HFP_HF_CODEC_NEG + bool "Codec Negotiation for HFP HF [EXPERIMENTAL]" + help + This option enables Codec Negotiation for HFP HF + +config BT_HFP_HF_ECNR + bool "EC and/or NR function for HFP HF [EXPERIMENTAL]" + help + This option enables EC and/or NR function for HFP HF + +if BT_HFP_HF_CODEC_NEG +config BT_HFP_HF_CODEC_MSBC + bool "Support Codec mSBC for HFP HF [EXPERIMENTAL]" + help + This option enables Codec mSBC for HFP HF + +config BT_HFP_HF_CODEC_LC3_SWB + bool "Support Codec LC3 SWB for HFP HF [EXPERIMENTAL]" + help + This option enables Codec LC3 SWB for HFP HF +endif # BT_HFP_HF_CODEC_NEG + +config BT_HFP_HF_3WAY_CALL + bool "Three-way calling for HFP HF [EXPERIMENTAL]" + help + This option enables Three-way calling for HFP HF + +config BT_HFP_HF_ECS + bool "Enhanced Call Status for HFP HF [EXPERIMENTAL]" + default y if BT_HFP_HF_3WAY_CALL + help + This option enables Enhanced Call Status for HFP HF + +config BT_HFP_HF_ECC + bool "Enhanced Call Control for HFP HF [EXPERIMENTAL]" + default y if BT_HFP_HF_3WAY_CALL + help + This option enables Enhanced Call Control for HFP HF + +config BT_HFP_HF_MAX_CALLS + int "Maximum supported calls for HFP HF [EXPERIMENTAL]" + default 2 if BT_HFP_HF_3WAY_CALL + default 1 + range 1 2 + help + This option sets maximum supported calls for HFP HF + +config BT_HFP_HF_VOICE_RECG + bool "Voice recognition activation for HFP HF [EXPERIMENTAL]" + help + This option enables Voice recognition activation for HFP HF + +config BT_HFP_HF_ENH_VOICE_RECG + bool "Enhanced Voice Recognition Status for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_VOICE_RECG + help + This option enables Enhanced Voice Recognition Status for HFP HF + +config BT_HFP_HF_VOICE_RECG_TEXT + bool "Voice Recognition Text for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_ENH_VOICE_RECG + help + This option enables Voice Recognition Text for HFP HF + +config BT_HFP_HF_HF_INDICATORS + bool "HF Indicators for HFP HF [EXPERIMENTAL]" + help + This option enables HF Indicators for HFP HF + +config BT_HFP_HF_HF_INDICATOR_ENH_SAFETY + bool "HF Indicator Enhanced Safety for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_HF_INDICATORS + help + This option enables HF Indicator Enhanced Safety for HFP HF + +config BT_HFP_HF_HF_INDICATOR_BATTERY + bool "HF Indicator Battery level for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_HF_INDICATORS + help + This option enables HF Indicator Battery level for HFP HF + +endif # BT_HFP_HF + if BT_HFP_AG +config BT_HFP_AG_EXT_ERR + bool "Extended Error Result Codes for HFP AG [EXPERIMENTAL]" + default y + help + This option enables Extended Error Result Codes for HFP AG + +config BT_HFP_AG_CODEC_NEG + bool "Codec Negotiation for HFP AG [EXPERIMENTAL]" + help + This option enables Codec Negotiation for HFP AG + +config BT_HFP_AG_ECNR + bool "EC and/or NR function for HFP AG [EXPERIMENTAL]" + help + This option enables EC and/or NR function for HFP AG + +config BT_HFP_AG_3WAY_CALL + bool "Three-way calling for HFP AG [EXPERIMENTAL]" + help + This option enables Three-way calling for HFP AG + +config BT_HFP_AG_MAX_CALLS + int "Maximum supported calls for HFP AG [EXPERIMENTAL]" + default 2 if BT_HFP_AG_3WAY_CALL + default 1 + range 1 2 + help + This option sets maximum supported calls for HFP AG + +config BT_HFP_AG_ECS + bool "Enhanced call status for HFP AG [EXPERIMENTAL]" + default y if BT_HFP_AG_3WAY_CALL + help + This option enables Enhanced call status for HFP AG + +config BT_HFP_AG_ECC + bool "Enhanced call control for HFP AG [EXPERIMENTAL]" + default y if BT_HFP_AG_3WAY_CALL + help + This option enables Enhanced call control for HFP AG + config BT_HFP_AG_TX_BUF_COUNT int "Maximum number of TX buffers for HFP AG [EXPERIMENTAL]" default BT_RFCOMM_TX_MAX @@ -235,6 +372,53 @@ config BT_HFP_AG_RING_NOTIFY_INTERVAL help Ring notification interval if the call is in alert state. The unit is seconds. + +config BT_HFP_AG_VOICE_RECG + bool "Voice recognition activation for HFP AG [EXPERIMENTAL]" + help + This option enables Voice recognition activation for HFP AG + +config BT_HFP_AG_ENH_VOICE_RECG + bool "Enhanced Voice Recognition Status for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_VOICE_RECG + help + This option enables Enhanced Voice Recognition Status for HFP AG + +config BT_HFP_AG_VOICE_RECG_TEXT + bool "Voice Recognition Text for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_ENH_VOICE_RECG + help + This option enables Voice Recognition Text for HFP AG + +config BT_HFP_AG_VOICE_TAG + bool "Attach a phone number for a voice tag for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_VOICE_RECG + help + This option enables Attach a phone number for a voice tag for HFP AG + +config BT_HFP_AG_HF_INDICATORS + bool "HF Indicators for HFP AG [EXPERIMENTAL]" + help + This option enables HF Indicators for HFP AG + +config BT_HFP_AG_HF_INDICATOR_ENH_SAFETY + bool "HF Indicator Enhanced Safety for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_HF_INDICATORS + help + This option enables HF Indicator Enhanced Safety for HFP AG + +config BT_HFP_AG_HF_INDICATOR_BATTERY + bool "HF Indicator Battery level for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_HF_INDICATORS + help + This option enables HF Indicator Battery level for HFP AG + +config BT_HFP_AG_REJECT_CALL + bool "Ability to reject a call for HFP AG [EXPERIMENTAL]" + default y + help + This option enables ability to reject a call for HFP AG + endif # BT_HFP_AG config BT_AVDTP diff --git a/subsys/bluetooth/host/classic/at.c b/subsys/bluetooth/host/classic/at.c index fadeeab10fb7c..b56ad286f06fd 100644 --- a/subsys/bluetooth/host/classic/at.c +++ b/subsys/bluetooth/host/classic/at.c @@ -119,24 +119,46 @@ static int get_cmd_value(struct at_client *at, struct net_buf *buf, return 0; } -static int get_response_string(struct at_client *at, struct net_buf *buf, - char stop_byte, enum at_state state) +static bool is_stop_byte(char target, char *stop_string) +{ + return (strchr(stop_string, target) != NULL); +} + +static bool is_vgm_or_vgs(struct at_client *at) +{ + if (!strcmp(at->buf, "VGM")) { + return true; + } + + if (!strcmp(at->buf, "VGS")) { + return true; + } + return false; +} + +static int get_response_string(struct at_client *at, struct net_buf *buf, char *stop_string, + enum at_state state) { int cmd_len = 0; uint8_t pos = at->pos; const char *str = (char *)buf->data; while (cmd_len < buf->len && at->pos != at->buf_max_len) { - if (*str != stop_byte) { + if (!is_stop_byte(*str, stop_string)) { at->buf[at->pos++] = *str; cmd_len++; str++; pos = at->pos; } else { + char stop_byte = at->buf[at->pos]; + cmd_len++; at->buf[at->pos] = '\0'; at->pos = 0U; at->state = state; + if ((stop_byte == '=') && !is_vgm_or_vgs(at)) { + return -EINVAL; + } break; } } @@ -197,7 +219,7 @@ static int at_state_start_lf(struct at_client *at, struct net_buf *buf) static int at_state_get_cmd_string(struct at_client *at, struct net_buf *buf) { - return get_response_string(at, buf, ':', AT_STATE_PROCESS_CMD); + return get_response_string(at, buf, ":=", AT_STATE_PROCESS_CMD); } static bool is_cmer(struct at_client *at) @@ -227,7 +249,7 @@ static int at_state_process_cmd(struct at_client *at, struct net_buf *buf) static int at_state_get_result_string(struct at_client *at, struct net_buf *buf) { - return get_response_string(at, buf, '\r', AT_STATE_PROCESS_RESULT); + return get_response_string(at, buf, "\r", AT_STATE_PROCESS_RESULT); } static bool is_ring(struct at_client *at) @@ -427,7 +449,7 @@ int at_parse_cmd_input(struct at_client *at, struct net_buf *buf, int at_has_next_list(struct at_client *at) { - return at->buf[at->pos] != '\0'; + return at->buf[at->pos] != '\0' && at->buf[at->pos] != ')'; } int at_open_list(struct at_client *at) @@ -535,3 +557,59 @@ void at_register(struct at_client *at, at_resp_cb_t resp, at_finish_cb_t finish) at->finish = finish; at->state = AT_STATE_START; } + +char *at_get_string(struct at_client *at) +{ + uint8_t pos = at->pos; + char *string; + + skip_space(at); + + if (at->buf[at->pos] != '"') { + at->pos = pos; + return NULL; + } + at->pos++; + string = &at->buf[at->pos]; + + while (at->buf[at->pos] != '\0' && at->buf[at->pos] != '"') { + at->pos++; + } + + if (at->buf[at->pos] != '"') { + at->pos = pos; + return NULL; + } + + at->buf[at->pos] = '\0'; + at->pos++; + + skip_space(at); + next_list(at); + + return string; +} + +char *at_get_raw_string(struct at_client *at, size_t *string_len) +{ + char *string; + + skip_space(at); + + string = &at->buf[at->pos]; + + while (at->buf[at->pos] != '\0' && + at->buf[at->pos] != ',' && + at->buf[at->pos] != ')') { + at->pos++; + } + + if (string_len) { + *string_len = &at->buf[at->pos] - string; + } + + skip_space(at); + next_list(at); + + return string; +} diff --git a/subsys/bluetooth/host/classic/at.h b/subsys/bluetooth/host/classic/at.h index ae2c7f95d8049..fc6ab68ad2fb5 100644 --- a/subsys/bluetooth/host/classic/at.h +++ b/subsys/bluetooth/host/classic/at.h @@ -116,3 +116,5 @@ int at_list_get_string(struct at_client *at, char *name, uint8_t len); int at_close_list(struct at_client *at); int at_open_list(struct at_client *at); int at_has_next_list(struct at_client *at); +char *at_get_string(struct at_client *at); +char *at_get_raw_string(struct at_client *at, size_t *string_len); diff --git a/subsys/bluetooth/host/classic/hfp_ag.c b/subsys/bluetooth/host/classic/hfp_ag.c index c9f92f1618cf4..8c12dde09a067 100644 --- a/subsys/bluetooth/host/classic/hfp_ag.c +++ b/subsys/bluetooth/host/classic/hfp_ag.c @@ -17,6 +17,7 @@ #include #include +#include #include "host/hci_core.h" #include "host/conn_internal.h" @@ -24,8 +25,6 @@ #include "rfcomm_internal.h" #include "at.h" #include "sco_internal.h" - -#include "hfp_internal.h" #include "hfp_ag_internal.h" #define LOG_LEVEL CONFIG_BT_HFP_AG_LOG_LEVEL @@ -74,21 +73,95 @@ static struct bt_hfp_ag bt_hfp_ag_pool[CONFIG_BT_MAX_CONN]; static struct bt_hfp_ag_cb *bt_ag; +#define AG_SUPT_FEAT(ag, _feature) ((ag->ag_features & (_feature)) != 0) +#define HF_SUPT_FEAT(ag, _feature) ((ag->hf_features & (_feature)) != 0) +#define BOTH_SUPT_FEAT(ag, _hf_feature, _ag_feature) \ + (HF_SUPT_FEAT(ag, _hf_feature) && AG_SUPT_FEAT(ag, _ag_feature)) + /* Sent but not acknowledged TX packets with a callback */ static struct bt_ag_tx ag_tx[CONFIG_BT_HFP_AG_TX_BUF_COUNT * 2]; static K_FIFO_DEFINE(ag_tx_free); static K_FIFO_DEFINE(ag_tx_notify); -struct k_thread ag_thread; -static K_KERNEL_STACK_MEMBER(ag_thread_stack, CONFIG_BT_HFP_AG_THREAD_STACK_SIZE); -static k_tid_t ag_thread_id; +/* HFP Gateway SDP record */ +static struct bt_sdp_attribute hfp_ag_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_AGW_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_GENERIC_AUDIO_SVCLASS) + } + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(BT_RFCOMM_CHAN_HFP_AG) + }, + ) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0109) + }, + ) + ), + + BT_SDP_LIST( + BT_SDP_ATTR_NETWORK, + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(IS_ENABLED(CONFIG_BT_HFP_AG_REJECT_CALL)) + ), + /* The values of the “SupportedFeatures” bitmap shall be the same as the + * values of the Bits 0 to 4 of the AT-command AT+BRSF (see Section 5.3). + */ + BT_SDP_SUPPORTED_FEATURES(BT_HFP_AG_SDP_SUPPORTED_FEATURES), +}; + +static struct bt_sdp_record hfp_ag_rec = BT_SDP_RECORD(hfp_ag_attrs); static enum at_cme bt_hfp_ag_get_cme_err(int err) { enum at_cme cme_err; switch (err) { - case -EOPNOTSUPP: + case -ENOEXEC: cme_err = CME_ERROR_OPERATION_NOT_SUPPORTED; break; case -EFAULT: @@ -98,7 +171,6 @@ static enum at_cme bt_hfp_ag_get_cme_err(int err) cme_err = CME_ERROR_MEMORY_FAILURE; break; case -ENOMEM: - __fallthrough; case -ENOBUFS: cme_err = CME_ERROR_MEMORY_FULL; break; @@ -134,12 +206,19 @@ static void hfp_ag_unlock(struct bt_hfp_ag *ag) static void bt_hfp_ag_set_state(struct bt_hfp_ag *ag, bt_hfp_state_t state) { - LOG_DBG("update state %p, old %d -> new %d", ag, ag->state, state); + bt_hfp_state_t old_state; hfp_ag_lock(ag); + old_state = ag->state; ag->state = state; hfp_ag_unlock(ag); + LOG_DBG("update state %p, old %d -> new %d", ag, old_state, state); + + if (old_state == state) { + return; + } + switch (state) { case BT_HFP_DISCONNECTED: if (bt_ag && bt_ag->disconnected) { @@ -152,7 +231,7 @@ static void bt_hfp_ag_set_state(struct bt_hfp_ag *ag, bt_hfp_state_t state) break; case BT_HFP_CONNECTED: if (bt_ag && bt_ag->connected) { - bt_ag->connected(ag); + bt_ag->connected(ag->acl_conn, ag); } break; case BT_HFP_DISCONNECTING: @@ -163,50 +242,70 @@ static void bt_hfp_ag_set_state(struct bt_hfp_ag *ag, bt_hfp_state_t state) } } -static void bt_hfp_ag_set_call_state(struct bt_hfp_ag *ag, bt_hfp_call_state_t call_state) +static struct bt_hfp_ag_call *get_call_from_number(struct bt_hfp_ag *ag, const char *number, + uint8_t type) { - bt_hfp_state_t state; + struct bt_hfp_ag_call *call; - LOG_DBG("update call state %p, old %d -> new %d", ag, ag->call_state, call_state); + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; - hfp_ag_lock(ag); - ag->call_state = call_state; - state = ag->state; - hfp_ag_unlock(ag); + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } - switch (call_state) { - case BT_HFP_CALL_TERMINATE: - atomic_clear(ag->flags); - k_work_cancel_delayable(&ag->deferred_work); - break; - case BT_HFP_CALL_OUTGOING: - k_work_reschedule(&ag->deferred_work, K_SECONDS(CONFIG_BT_HFP_AG_OUTGOING_TIMEOUT)); - break; - case BT_HFP_CALL_INCOMING: - k_work_reschedule(&ag->deferred_work, K_SECONDS(CONFIG_BT_HFP_AG_INCOMING_TIMEOUT)); - break; - case BT_HFP_CALL_ALERTING: - k_work_reschedule(&ag->ringing_work, K_NO_WAIT); - k_work_reschedule(&ag->deferred_work, K_SECONDS(CONFIG_BT_HFP_AG_ALERTING_TIMEOUT)); - break; - case BT_HFP_CALL_ACTIVE: - k_work_cancel_delayable(&ag->ringing_work); - k_work_cancel_delayable(&ag->deferred_work); - break; - case BT_HFP_CALL_HOLD: - break; - default: - /* Invalid state */ - break; + if (call->type != type) { + continue; + } + + if (strcmp(call->number, number)) { + continue; + } + + return call; } - if (state == BT_HFP_DISCONNECTING) { - int err = bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); + return NULL; +} - if (err) { - LOG_ERR("Fail to disconnect DLC %p", &ag->rfcomm_dlc); +static void bt_ag_deferred_work(struct k_work *work); +static void bt_ag_ringing_work(struct k_work *work); + +static struct bt_hfp_ag_call *get_new_call(struct bt_hfp_ag *ag, const char *number, uint8_t type) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_and_set_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + /* Copy number to ag->number including null-character */ + strcpy(call->number, number); + call->type = type; + call->ag = ag; + k_work_init_delayable(&call->deferred_work, bt_ag_deferred_work); + k_work_init_delayable(&call->ringing_work, bt_ag_ringing_work); + return call; + } + } + + return NULL; +} + +static int get_none_released_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + count++; } } + + return count; } static struct bt_ag_tx *bt_ag_tx_alloc(void) @@ -266,67 +365,11 @@ static int hfp_ag_next_step(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *us return 0; } -static int hfp_ag_send(struct bt_hfp_ag *ag, struct bt_ag_tx *tx) -{ - int err = bt_rfcomm_dlc_send(&ag->rfcomm_dlc, tx->buf); - - if (err < 0) { - net_buf_unref(tx->buf); - } - - return err; -} - -static void bt_ag_tx_work(struct k_work *work) -{ - struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, tx_work); - sys_snode_t *node; - struct bt_ag_tx *tx; - bt_hfp_state_t state; - - hfp_ag_lock(ag); - state = ag->state; - if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) { - LOG_ERR("AG %p is not connected", ag); - goto unlock; - } - - node = sys_slist_peek_head(&ag->tx_pending); - if (!node) { - LOG_DBG("No pending tx"); - goto unlock; - } - - tx = CONTAINER_OF(node, struct bt_ag_tx, node); - - if (!atomic_test_and_set_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { - LOG_DBG("AG %p sending tx %p", ag, tx); - int err = hfp_ag_send(ag, tx); - - if (err < 0) { - LOG_ERR("Rfcomm send error :(%d)", err); - sys_slist_find_and_remove(&ag->tx_pending, &tx->node); - tx->err = err; - k_fifo_put(&ag_tx_notify, tx); - /* Clear the tx ongoing flag */ - if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { - LOG_WRN("tx ongoing flag is not set"); - } - /* Due to the work is failed, restart the tx work */ - k_work_reschedule(&ag->tx_work, K_NO_WAIT); - } - } - -unlock: - hfp_ag_unlock(ag); -} - static int hfp_ag_send_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *user_data, const char *format, ...) { - struct net_buf *buf; - struct bt_ag_tx *tx; + struct net_buf *buf = NULL; + struct bt_ag_tx *tx = NULL; va_list vargs; int err; bt_hfp_state_t state; @@ -338,20 +381,22 @@ static int hfp_ag_send_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *us hfp_ag_unlock(ag); if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) { LOG_ERR("AG %p is not connected", ag); - return -ENOTCONN; + err = -ENOTCONN; + goto failed; } buf = bt_rfcomm_create_pdu(&ag_pool); if (!buf) { LOG_ERR("No Buffers!"); - return -ENOMEM; + err = -ENOMEM; + goto failed; } tx = bt_ag_tx_alloc(); if (tx == NULL) { LOG_ERR("No tx buffers!"); - net_buf_unref(buf); - return -ENOMEM; + err = -ENOMEM; + goto failed; } LOG_DBG("buf %p tx %p", buf, tx); @@ -367,9 +412,7 @@ static int hfp_ag_send_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *us if (err < 0) { LOG_ERR("Unable to format variable arguments"); - net_buf_unref(buf); - bt_ag_tx_free(tx); - return err; + goto failed; } net_buf_add(buf, err); @@ -384,133 +427,2266 @@ static int hfp_ag_send_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *us k_work_reschedule(&ag->tx_work, K_NO_WAIT); return 0; -} -static void skip_space(struct net_buf *buf) -{ - while ((buf->len > 0) && (buf->data[0] == ' ')) { - (void)net_buf_pull(buf, 1); +failed: + if (buf) { + net_buf_unref(buf); + } + + if (tx) { + bt_ag_tx_free(tx); } + bt_hfp_ag_disconnect(ag); + return err; } -static int get_number(struct net_buf *buf, uint32_t *number) +static int hfp_ag_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, + uint8_t value, bt_hfp_ag_tx_cb_t cb, void *user_data) { - int err = -EINVAL; + int err; + uint8_t old_value; - *number = 0; + if (index >= BT_HFP_AG_IND_MAX) { + return -EINVAL; + } - skip_space(buf); - while (buf->len > 0) { - if ((buf->data[0] >= '0') && (buf->data[0] <= '9')) { - *number = *number * 10 + buf->data[0] - '0'; - (void)net_buf_pull(buf, 1); - err = 0; - } else { - break; + if ((ag_ind[index].max < value) || (ag_ind[index].min > value)) { + return -EINVAL; + } + + hfp_ag_lock(ag); + old_value = ag->indicator_value[index]; + if (value == old_value) { + LOG_ERR("Duplicate value setting, indicator %d, old %d -> new %d", index, old_value, + value); + hfp_ag_unlock(ag); + if (cb) { + cb(ag, user_data); } + return -EINVAL; } - skip_space(buf); - return err; -} + ag->indicator_value[index] = value; + hfp_ag_unlock(ag); -static bool is_char(struct net_buf *buf, uint8_t c) -{ - bool found = false; + LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); - skip_space(buf); - if (buf->len > 0) { - if (buf->data[0] == c) { - (void)net_buf_pull(buf, 1); - found = true; - } + if (!(ag->indicator & BIT(index))) { + LOG_INF("The indicator %d is deactivated", index); + /* If the indicator is deactivated, consider it a successful set. */ + return 0; } - skip_space(buf); - return found; + err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", (uint8_t)index + 1, value); + if (err) { + hfp_ag_lock(ag); + ag->indicator_value[index] = old_value; + hfp_ag_unlock(ag); + LOG_ERR("Fail to update indicator %d, current %d", index, old_value); + } + + return err; } -static int bt_hfp_ag_brsf_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static int hfp_ag_force_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, + uint8_t value, bt_hfp_ag_tx_cb_t cb, void *user_data) { - uint32_t hf_features; int err; + uint8_t old_value; - if (!is_char(buf, '=')) { - return -ENOTSUP; - } + hfp_ag_lock(ag); + old_value = ag->indicator_value[index]; + ag->indicator_value[index] = value; + hfp_ag_unlock(ag); - err = get_number(buf, &hf_features); - if (err != 0) { - return -ENOTSUP; - } + LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); - if (!is_char(buf, '\r')) { - return -ENOTSUP; + err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", (uint8_t)index + 1, value); + if (err) { + hfp_ag_lock(ag); + ag->indicator_value[index] = old_value; + hfp_ag_unlock(ag); + LOG_ERR("Fail to update indicator %d, current %d", index, old_value); } - hfp_ag_lock(ag); - ag->hf_features = hf_features; - hfp_ag_unlock(ag); - - return hfp_ag_send_data(ag, NULL, NULL, "\r\n+BRSF:%d\r\n", ag->ag_features); + return err; } -static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static void clear_call_setup_ind_cb(struct bt_hfp_ag *ag, void *user_data) { - uint32_t codec; - uint32_t codec_ids = 0U; - int err = 0; - - if (!is_char(buf, '=')) { - return -ENOTSUP; - } + uint8_t call_ind; + int err; hfp_ag_lock(ag); - if (!(ag->hf_features & BT_HFP_HF_FEATURE_CODEC_NEG) || - !(ag->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG)) { - hfp_ag_unlock(ag); - return -EOPNOTSUPP; - } + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; hfp_ag_unlock(ag); - while (buf->len > 0) { - err = get_number(buf, &codec); - if (err != 0) { - return -ENOTSUP; - } - - if (!is_char(buf, ',')) { - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } - } - - if (codec < (sizeof(codec_ids) * 8)) { - codec_ids |= BIT(codec); + if (call_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, NULL, NULL); + if (err) { + LOG_ERR("Fail to clear call_setup ind"); } } +} + +static void clear_call_ind_cb(struct bt_hfp_ag *ag, void *user_data) +{ + uint8_t call_setup_ind; + int err; hfp_ag_lock(ag); - ag->hf_codec_ids = codec_ids; + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; hfp_ag_unlock(ag); - if (bt_ag && bt_ag->codec) { - bt_ag->codec(ag, ag->hf_codec_ids); + if (call_setup_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + NULL, NULL); + if (err) { + LOG_ERR("Fail to clear call_setup ind"); + } } - - return 0; } -static int bt_hfp_ag_cind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static void clear_call_and_call_setup_ind(struct bt_hfp_ag *ag) { + uint8_t call_setup_ind; + uint8_t call_ind; int err; - bool inquiry_status = true; - if (is_char(buf, '=')) { - inquiry_status = false; - } + hfp_ag_lock(ag); + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; + hfp_ag_unlock(ag); + + if (call_setup_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + clear_call_setup_ind_cb, NULL); + if (err) { + LOG_ERR("Fail to clear call_setup ind"); + } + } + + if (call_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, clear_call_ind_cb, NULL); + if (err) { + LOG_ERR("Fail to clear call ind"); + } + } +} + +static int get_active_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + count++; + } + hfp_ag_unlock(ag); + } + } + + return count; +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static int get_active_held_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_HOLD) || + ((call->call_state == BT_HFP_CALL_ACTIVE) && + (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)))) { + count++; + } + hfp_ag_unlock(ag); + } + } + + return count; +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +static int get_held_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + count++; + } else if (call->call_state == BT_HFP_CALL_HOLD) { + count++; + } + hfp_ag_unlock(ag); + } + } + + return count; +} + +static void ag_notify_call_held_ind(struct bt_hfp_ag *ag, void *user_data) +{ + int active_call_count; + int held_call_count; + int err; + uint8_t value; + + active_call_count = get_active_calls(ag); + held_call_count = get_held_calls(ag); + if (active_call_count && held_call_count) { + value = BT_HFP_CALL_HELD_ACTIVE_HELD; + } else if (held_call_count) { + value = BT_HFP_CALL_HELD_HELD; + } else { + value = BT_HFP_CALL_HELD_NONE; + } + + err = hfp_ag_force_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, value, NULL, NULL); + if (err) { + LOG_ERR("Fail to notify call_held ind :(%d)", err); + } +} + +static void free_call(struct bt_hfp_ag_call *call) +{ + int call_count; + struct bt_hfp_ag *ag; + int err; + + ag = call->ag; + + k_work_cancel_delayable(&call->ringing_work); + k_work_cancel_delayable(&call->deferred_work); + memset(call, 0, sizeof(*call)); + + call_count = get_none_released_calls(ag); + + if (!call_count) { + clear_call_and_call_setup_ind(ag); + } else { + err = hfp_ag_next_step(ag, ag_notify_call_held_ind, NULL); + if (err) { + LOG_ERR("Fail to notify call_held ind :(%d)", err); + } + } +} + +static void bt_hfp_ag_set_call_state(struct bt_hfp_ag_call *call, bt_hfp_call_state_t call_state) +{ + bt_hfp_state_t state; + struct bt_hfp_ag *ag = call->ag; + + LOG_DBG("update call state %p, old %d -> new %d", call, call->call_state, call_state); + + hfp_ag_lock(ag); + call->call_state = call_state; + state = ag->state; + hfp_ag_unlock(ag); + + switch (call_state) { + case BT_HFP_CALL_TERMINATE: + atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + free_call(call); + break; + case BT_HFP_CALL_OUTGOING: + k_work_reschedule(&call->deferred_work, + K_SECONDS(CONFIG_BT_HFP_AG_OUTGOING_TIMEOUT)); + break; + case BT_HFP_CALL_INCOMING: + k_work_reschedule(&call->deferred_work, + K_SECONDS(CONFIG_BT_HFP_AG_INCOMING_TIMEOUT)); + break; + case BT_HFP_CALL_ALERTING: + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { + k_work_reschedule(&call->ringing_work, K_NO_WAIT); + } else { + k_work_cancel_delayable(&call->ringing_work); + } + k_work_reschedule(&call->deferred_work, + K_SECONDS(CONFIG_BT_HFP_AG_ALERTING_TIMEOUT)); + break; + case BT_HFP_CALL_ACTIVE: + k_work_cancel_delayable(&call->ringing_work); + k_work_cancel_delayable(&call->deferred_work); + break; + case BT_HFP_CALL_HOLD: + break; + default: + /* Invalid state */ + break; + } + + if (state == BT_HFP_DISCONNECTING) { + int err = bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); + + if (err) { + LOG_ERR("Fail to disconnect DLC %p", &ag->rfcomm_dlc); + } + } +} + +static void hfp_ag_close_sco(struct bt_hfp_ag *ag) +{ + struct bt_conn *sco; + int call_count; + + LOG_DBG(""); + + hfp_ag_lock(ag); + call_count = get_none_released_calls(ag); + if (call_count > 0) { + LOG_INF("Do not close sco because not all calls are released"); + hfp_ag_unlock(ag); + return; + } + + sco = ag->sco_chan.sco; + ag->sco_chan.sco = NULL; + hfp_ag_unlock(ag); + if (sco != NULL) { + LOG_DBG("Disconnect sco %p", sco); + bt_conn_disconnect(sco, BT_HCI_ERR_LOCALHOST_TERM_CONN); + } +} + +static void ag_reject_call(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + + ag = call->ag; + + if (bt_ag && bt_ag->reject) { + bt_ag->reject(call); + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static void ag_terminate_call(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + + ag = call->ag; + + if (bt_ag && bt_ag->terminate) { + bt_ag->terminate(call); + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static int hfp_ag_send(struct bt_hfp_ag *ag, struct bt_ag_tx *tx) +{ + int err = bt_rfcomm_dlc_send(&ag->rfcomm_dlc, tx->buf); + + if (err < 0) { + net_buf_unref(tx->buf); + } + + return err; +} + +static void bt_ag_tx_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, tx_work); + sys_snode_t *node; + struct bt_ag_tx *tx; + bt_hfp_state_t state; + + hfp_ag_lock(ag); + state = ag->state; + if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) { + LOG_ERR("AG %p is not connected", ag); + goto unlock; + } + + node = sys_slist_peek_head(&ag->tx_pending); + if (!node) { + LOG_DBG("No pending tx"); + goto unlock; + } + + tx = CONTAINER_OF(node, struct bt_ag_tx, node); + + if (!atomic_test_and_set_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { + LOG_DBG("AG %p sending tx %p", ag, tx); + int err = hfp_ag_send(ag, tx); + + if (err < 0) { + LOG_ERR("Rfcomm send error :(%d)", err); + sys_slist_find_and_remove(&ag->tx_pending, &tx->node); + tx->err = err; + k_fifo_put(&ag_tx_notify, tx); + /* Clear the tx ongoing flag */ + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { + LOG_WRN("tx ongoing flag is not set"); + } + /* Due to the work is failed, restart the tx work */ + k_work_reschedule(&ag->tx_work, K_NO_WAIT); + } + } + +unlock: + hfp_ag_unlock(ag); +} + +static void skip_space(struct net_buf *buf) +{ + size_t count = 0; + + while ((buf->len > count) && (buf->data[count] == ' ')) { + count++; + } + + if (count > 0) { + (void)net_buf_pull(buf, count); + } +} + +static int get_number(struct net_buf *buf, uint32_t *number) +{ + int err = -EINVAL; + + *number = 0; + + skip_space(buf); + while (buf->len > 0) { + if ((buf->data[0] >= '0') && (buf->data[0] <= '9')) { + *number = *number * 10 + buf->data[0] - '0'; + (void)net_buf_pull(buf, 1); + err = 0; + } else { + break; + } + } + skip_space(buf); + + return err; +} + +static int get_char(struct net_buf *buf, char *c) +{ + int err = -EINVAL; + + skip_space(buf); + if (buf->len > 0) { + *c = (char)buf->data[0]; + (void)net_buf_pull(buf, 1); + err = 0; + } + skip_space(buf); + + return err; +} + +static bool is_char(struct net_buf *buf, uint8_t c) +{ + bool found = false; + + skip_space(buf); + if (buf->len > 0) { + if (buf->data[0] == c) { + (void)net_buf_pull(buf, 1); + found = true; + } + } + skip_space(buf); + + return found; +} + +static int bt_hfp_ag_brsf_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t hf_features; + int err; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &hf_features); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + ag->hf_features = hf_features; + + return hfp_ag_send_data(ag, NULL, NULL, "\r\n+BRSF:%d\r\n", ag->ag_features); +} + +static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t codec; + uint32_t codec_ids = 0U; + int err = 0; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_CODEC_NEG, BT_HFP_AG_FEATURE_CODEC_NEG)) { + return -ENOEXEC; + } + + while (buf->len > 0) { + err = get_number(buf, &codec); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) { + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + } + + if (codec < NUM_BITS(sizeof(codec_ids))) { + codec_ids |= BIT(codec); + } + } + + if (!(codec_ids & BIT(BT_HFP_AG_CODEC_CVSD))) { + return -ENOEXEC; + } + + hfp_ag_lock(ag); + ag->hf_codec_ids = codec_ids; + ag->selected_codec_id = 0; + hfp_ag_unlock(ag); + + atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + + if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { + /* Codec connection is ended. It needs to be restarted. */ + LOG_DBG("Codec connection is ended. It needs to be restarted."); + if (bt_ag && bt_ag->codec_negotiate) { + bt_ag->codec_negotiate(ag, -EAGAIN); + } + } + + if (bt_ag && bt_ag->codec) { + bt_ag->codec(ag, ag->hf_codec_ids); + } + + return 0; +} + +static int bt_hfp_ag_cind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + bool inquiry_status = true; + + if (is_char(buf, '=')) { + inquiry_status = false; + } + + if (!is_char(buf, '?')) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (!inquiry_status) { + err = hfp_ag_send_data( + ag, NULL, NULL, + "\r\n+CIND:(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%" + "d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d))\r\n", + ag_ind[BT_HFP_AG_SERVICE_IND].name, ag_ind[BT_HFP_AG_SERVICE_IND].min, + ag_ind[BT_HFP_AG_SERVICE_IND].connector, ag_ind[BT_HFP_AG_SERVICE_IND].max, + ag_ind[BT_HFP_AG_CALL_IND].name, ag_ind[BT_HFP_AG_CALL_IND].min, + ag_ind[BT_HFP_AG_CALL_IND].connector, ag_ind[BT_HFP_AG_CALL_IND].max, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].name, ag_ind[BT_HFP_AG_CALL_SETUP_IND].min, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].connector, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].max, ag_ind[BT_HFP_AG_CALL_HELD_IND].name, + ag_ind[BT_HFP_AG_CALL_HELD_IND].min, + ag_ind[BT_HFP_AG_CALL_HELD_IND].connector, + ag_ind[BT_HFP_AG_CALL_HELD_IND].max, ag_ind[BT_HFP_AG_SIGNAL_IND].name, + ag_ind[BT_HFP_AG_SIGNAL_IND].min, ag_ind[BT_HFP_AG_SIGNAL_IND].connector, + ag_ind[BT_HFP_AG_SIGNAL_IND].max, ag_ind[BT_HFP_AG_ROAM_IND].name, + ag_ind[BT_HFP_AG_ROAM_IND].min, ag_ind[BT_HFP_AG_ROAM_IND].connector, + ag_ind[BT_HFP_AG_ROAM_IND].max, ag_ind[BT_HFP_AG_BATTERY_IND].name, + ag_ind[BT_HFP_AG_BATTERY_IND].min, ag_ind[BT_HFP_AG_BATTERY_IND].connector, + ag_ind[BT_HFP_AG_BATTERY_IND].max); + } else { + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%d,%d,%d,%d,%d,%d,%d\r\n", + ag->indicator_value[BT_HFP_AG_SERVICE_IND], + ag->indicator_value[BT_HFP_AG_CALL_IND], + ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND], + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND], + ag->indicator_value[BT_HFP_AG_SIGNAL_IND], + ag->indicator_value[BT_HFP_AG_ROAM_IND], + ag->indicator_value[BT_HFP_AG_BATTERY_IND]); + } + + return err; +} + +static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data) +{ + bool is_inband_ringtone; + + is_inband_ringtone = AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false; + + if (is_inband_ringtone && !atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { + int err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR:1\r\n"); + + atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, err == 0); + } +} + +static void bt_hfp_ag_slc_connected(struct bt_hfp_ag *ag, void *user_data) +{ + bt_hfp_ag_set_state(ag, BT_HFP_CONNECTED); + (void)hfp_ag_next_step(ag, bt_hfp_ag_set_in_band_ring, NULL); +} + +static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t number; + int err; + static const uint32_t command_line_prefix[] = {3, 0, 0}; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) { + err = get_number(buf, &number); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) { + return -ENOTSUP; + } + + if (command_line_prefix[i] != number) { + return -ENOTSUP; + } + } + + err = get_number(buf, &number); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (number == 1) { + atomic_set_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); + if (BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_DBG("Waiting for AT+CHLD=?"); + return 0; + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_slc_connected, NULL); + return err; + } else if (number == 0) { + atomic_clear_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); + } else { + return -ENOTSUP; + } + + return 0; +} + +static void bt_hfp_ag_accept_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (bt_ag && bt_ag->accept) { + bt_ag->accept(call); + } +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static int chld_release_all(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state != BT_HFP_CALL_ACTIVE) || + ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + ag_terminate_call(call); + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static void bt_hfp_ag_accept_other_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + bt_hfp_call_state_t call_state; + int err; + + if (!call) { + return; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (call_state == BT_HFP_CALL_HOLD) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, + BT_HFP_CALL_HELD_ACTIVE_HELD, NULL, NULL); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + } + + if (call_state == BT_HFP_CALL_HOLD) { + if (bt_ag && bt_ag->retrieve) { + bt_ag->retrieve(call); + } + } else { + if (bt_ag && bt_ag->accept) { + bt_ag->accept(call); + } + } + + ag_notify_call_held_ind(ag, NULL); +} + +static void bt_hfp_ag_deactivate_accept_other_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + uint8_t call_setup; + + hfp_ag_lock(ag); + call_setup = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + hfp_ag_unlock(ag); + + if (call_setup) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_accept_other_cb, user_data); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + } else { + bt_hfp_ag_accept_other_cb(ag, user_data); + } +} + +static struct bt_hfp_ag_call *get_none_accept_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_OUTGOING) || (call_state == BT_HFP_CALL_INCOMING) || + (call_state == BT_HFP_CALL_ALERTING)) { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_ag_call *get_none_active_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state != BT_HFP_CALL_ACTIVE) { + return call; + } else if ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + return call; + } + } + + return NULL; +} + +static int chld_deactivate_calls_and_accept_other_call(struct bt_hfp_ag *ag, bool release) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *none_active_call; + + none_active_call = get_none_accept_calls(ag); + + if (!none_active_call) { + none_active_call = get_none_active_calls(ag); + } else { + if (!(atomic_test_bit(none_active_call->flags, BT_HFP_AG_CALL_INCOMING_3WAY) || + atomic_test_bit(none_active_call->flags, BT_HFP_AG_CALL_INCOMING))) { + return -ENOTSUP; + } + } + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + if (release) { + ag_terminate_call(call); + } else { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + + if (bt_ag && bt_ag->held) { + bt_ag->held(call); + } + } + } + } + + return hfp_ag_next_step(ag, bt_hfp_ag_deactivate_accept_other_cb, none_active_call); +} + +static int chld_activate_held_call(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state == BT_HFP_CALL_HOLD) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (bt_ag && bt_ag->retrieve) { + bt_ag->retrieve(call); + } + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_drop_conversation(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state != BT_HFP_CALL_ACTIVE) { + return -ENOTSUP; + } + } + + if (!bt_ag || !bt_ag->explicit_call_transfer) { + return -ENOEXEC; + } + + bt_ag->explicit_call_transfer(ag); + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("Close SCO connection"); + hfp_ag_close_sco(ag); + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_release_call(struct bt_hfp_ag *ag, uint8_t call_index) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + if (index != call_index) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_HOLD) || + ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + ag_terminate_call(call); + } else { + ag_reject_call(call); + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_held_other_calls(struct bt_hfp_ag *ag, uint8_t call_index) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + if (index == call_index) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + + if (bt_ag && bt_ag->held) { + bt_ag->held(call); + } + } + } + + call = &ag->calls[call_index]; + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state == BT_HFP_CALL_HOLD) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + if (bt_ag && bt_ag->retrieve) { + bt_ag->retrieve(call); + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_other(struct bt_hfp_ag *ag, uint32_t value) +{ + uint8_t index; + uint8_t command; + + index = value % 10; + command = value / 10; + + if (!index) { + return -ENOEXEC; + } + + index = index - 1; + + if (index >= ARRAY_SIZE(ag->calls)) { + return -ENOEXEC; + } + + switch (command) { + case BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER: + return chld_release_call(ag, index); + case BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER: + return chld_held_other_calls(ag, index); + } + + return -ENOEXEC; +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + uint32_t value; + int err; + char *response; + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { + return -ENOEXEC; + } + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + if (is_char(buf, '?')) { + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + +#if defined(CONFIG_BT_HFP_AG_ECC) + response = "+CHLD:(0,1,1x,2,2x,3,4)"; +#else + response = "+CHLD:(0,1,2,3,4)"; +#endif /* CONFIG_BT_HFP_AG_ECC */ + err = hfp_ag_send_data(ag, bt_hfp_ag_slc_connected, NULL, "\r\n%s\r\n", response); + return err; + } + + err = get_number(buf, &value); + if (err != 0) { + return -ENOTSUP; + } + + switch (value) { + case BT_HFP_CHLD_RELEASE_ALL: + return chld_release_all(ag); + case BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER: + return chld_deactivate_calls_and_accept_other_call(ag, true); + case BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER: + return chld_deactivate_calls_and_accept_other_call(ag, false); + case BT_HFP_CALL_ACTIVE_HELD: + return chld_activate_held_call(ag); + case BT_HFP_CALL_QUITE: + return chld_drop_conversation(ag); + } + + return chld_other(ag, value); +#else + return -ENOEXEC; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ +} + +static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t indicator; + uint32_t hf_indicators = 0U; + uint32_t supported_indicators = 0U; + int err; + char *data; + uint32_t len; + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_HF_IND, BT_HFP_AG_FEATURE_HF_IND)) { + return -ENOEXEC; + } + + if (is_char(buf, '?')) { + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + hf_indicators = ag->hf_indicators; + supported_indicators = ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; + len = MIN(NUM_BITS(sizeof(supported_indicators)), HFP_HF_IND_MAX); + for (int i = 1; i < len; i++) { + bool enabled; + + if (!(BIT(i) & supported_indicators)) { + continue; + } + + enabled = BIT(i) & hf_indicators; + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", i, + enabled ? 1 : 0); + if (err) { + return err; + } + + supported_indicators &= ~BIT(i); + + if (!supported_indicators) { + break; + } + } + return 0; + } + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + if (is_char(buf, '?')) { + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + data = &ag->buffer[0]; + *data = '('; + data++; + hf_indicators = ag->hf_indicators_of_ag; + len = MIN(NUM_BITS(sizeof(hf_indicators)), HFP_HF_IND_MAX); + for (int i = 1; (i < len) && (hf_indicators != 0); i++) { + if (BIT(i) & hf_indicators) { + int length = snprintk( + data, (char *)&ag->buffer[HF_MAX_BUF_LEN - 1] - data - 2, + "%d", i); + data += length; + hf_indicators &= ~BIT(i); + } + if (hf_indicators != 0) { + *data = ','; + data++; + } + } + *data = ')'; + data++; + *data = '\0'; + data++; + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%s\r\n", &ag->buffer[0]); + return err; + } + + while (buf->len > 0) { + err = get_number(buf, &indicator); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) { + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + } + + if (indicator < NUM_BITS(sizeof(hf_indicators))) { + hf_indicators |= BIT(indicator); + } + } + + ag->hf_indicators_of_hf = hf_indicators; + + return 0; +} + +static int bt_hfp_ag_cmee_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t cmee; + int err; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &cmee); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (cmee > 1) { + return -ENOTSUP; + } + + if (BT_HFP_AG_FEATURE_EXT_ERR_ENABLE) { + atomic_set_bit_to(ag->flags, BT_HFP_AG_CMEE_ENABLE, cmee == 1); + return 0; + } + + return -ENOTSUP; +} + +static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (call) { + ag_reject_call(call); + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data) +{ + int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, user_data); + if (err != 0) { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +static void bt_hfp_ag_terminate_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (call) { + ag_terminate_call(call); + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static void bt_hfp_ag_unit_call_terminate(struct bt_hfp_ag *ag, void *user_data) +{ + int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, + user_data); + if (err != 0) { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +static void bt_hfp_ag_active_terminate_cb(struct bt_hfp_ag *ag, void *user_data) +{ + bt_hfp_ag_unit_call_terminate(ag, user_data); +} + +static void bt_hfp_ag_call_terminate(struct bt_hfp_ag *ag, void *user_data) +{ + int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_active_terminate_cb, user_data); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +struct bt_hfp_ag_call *get_call_with_flag(struct bt_hfp_ag *ag, int bit, bool clear) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + if (clear) { + if (atomic_test_and_set_bit(call->flags, bit)) { + return call; + } + } else { + if (atomic_test_bit(call->flags, bit)) { + return call; + } + } + } + + return NULL; +} + +struct bt_hfp_ag_call *get_call_with_flag_and_state(struct bt_hfp_ag *ag, int bit, + bt_hfp_call_state_t state) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + if ((call->call_state & state) && atomic_test_bit(call->flags, bit)) { + return call; + } + } + + return NULL; +} + +static int bt_hfp_ag_chup_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call; + int call_count; + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + call_count = get_none_released_calls(ag); + + if (call_count == 1) { + bt_hfp_ag_tx_cb_t next_step = NULL; + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING, false); + if (!call) { + return 0; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state == BT_HFP_CALL_ALERTING) { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + next_step = bt_hfp_ag_call_terminate; + } else { + next_step = bt_hfp_ag_call_reject; + } + } else if (call_state == BT_HFP_CALL_ACTIVE) { + next_step = bt_hfp_ag_unit_call_terminate; + } + if (next_step) { + err = hfp_ag_next_step(ag, next_step, call); + return err; + } + + return -ENOTSUP; + } + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state == BT_HFP_CALL_ACTIVE) { + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, call); + } + } + return 0; +} + +static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag_call *call) +{ + uint8_t status = 0; + struct bt_hfp_ag *ag = call->ag; + + hfp_ag_lock(ag); + switch (call->call_state) { + case BT_HFP_CALL_TERMINATE: + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + status = BT_HFP_CLCC_STATUS_INCOMING; + } else { + status = BT_HFP_CLCC_STATUS_DIALING; + } + break; + case BT_HFP_CALL_OUTGOING: + status = BT_HFP_CLCC_STATUS_DIALING; + break; + case BT_HFP_CALL_INCOMING: + status = BT_HFP_CLCC_STATUS_INCOMING; + break; + case BT_HFP_CALL_ALERTING: + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + status = BT_HFP_CLCC_STATUS_WAITING; + } else { + status = BT_HFP_CLCC_STATUS_ALERTING; + } + break; + case BT_HFP_CALL_ACTIVE: + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + status = BT_HFP_CLCC_STATUS_CALL_HELD_HOLD; + } else { + status = BT_HFP_CLCC_STATUS_ACTIVE; + } + break; + case BT_HFP_CALL_HOLD: + status = BT_HFP_CLCC_STATUS_HELD; + break; + default: + break; + } + hfp_ag_unlock(ag); + + return status; +} + +static int bt_hfp_ag_clcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + struct bt_hfp_ag_call *call; + uint8_t dir; + uint8_t status; + uint8_t mode; + uint8_t mpty; + int active_calls; + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + active_calls = get_active_calls(ag); + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + dir = atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) ? 1 : 0; + status = bt_hfp_get_call_state(call); + mode = 0; + mpty = (status == BT_HFP_CLCC_STATUS_ACTIVE) && (active_calls > 1) ? 1 : 0; + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLCC:%d,%d,%d,%d,%d,\"%s\",%d\r\n", + index + 1, dir, status, mode, mpty, call->number, + call->type); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + } + + /* AG shall always send OK response to HF */ + return 0; +} + +static int bt_hfp_ag_bia_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t number; + int err; + int index = 0; + uint32_t indicator; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + indicator = ag->indicator; + hfp_ag_unlock(ag); + + while (buf->len > 0) { + err = get_number(buf, &number); + if (err == 0) { + /* Valid number */ + if (number) { + indicator |= BIT(index); + } else { + indicator &= ~BIT(index); + } + } + + if (is_char(buf, ',')) { + index++; + } else { + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + if (buf->len != 0) { + return -ENOTSUP; + } + } + } + + /* Force call, call setup and held call indicators are enabled. */ + indicator |= BIT(BT_HFP_AG_CALL_IND) | BIT(BT_HFP_AG_CALL_SETUP_IND) | + BIT(BT_HFP_AG_CALL_HELD_IND); + + hfp_ag_lock(ag); + ag->indicator = indicator; + hfp_ag_unlock(ag); + + return 0; +} + +static void bt_hfp_ag_call_ringing_cb(struct bt_hfp_ag_call *call, bool in_bond) +{ + if (bt_ag && bt_ag->ringing) { + bt_ag->ringing(call, in_bond); + } +} + +static void hfp_ag_sco_connected(struct bt_sco_chan *chan) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); + bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call; + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, true); + + if (call) { + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + if (call_state == BT_HFP_CALL_INCOMING) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, true); + } + } + + if ((bt_ag) && bt_ag->sco_connected) { + bt_ag->sco_connected(ag, chan->sco); + } +} + +static void hfp_ag_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); + bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call; + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, true); + + if ((bt_ag) && bt_ag->sco_disconnected) { + bt_ag->sco_disconnected(ag); + } + + if (!call) { + return; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + if ((call_state == BT_HFP_CALL_INCOMING) || (call_state == BT_HFP_CALL_OUTGOING)) { + bt_hfp_ag_call_reject(ag, call); + } +} + +static struct bt_conn *bt_hfp_ag_create_sco(struct bt_hfp_ag *ag) +{ + struct bt_conn *sco_conn; + + static struct bt_sco_chan_ops ops = { + .connected = hfp_ag_sco_connected, + .disconnected = hfp_ag_sco_disconnected, + }; + + LOG_DBG(""); + + if (ag->sco_chan.sco == NULL) { + ag->sco_chan.ops = &ops; + + /* create SCO connection*/ + sco_conn = bt_conn_create_sco(&ag->acl_conn->br.dst, &ag->sco_chan); + if (sco_conn != NULL) { + LOG_DBG("Created sco %p", sco_conn); + bt_conn_unref(sco_conn); + } + } else { + sco_conn = ag->sco_chan.sco; + } + + return sco_conn; +} + +static int hfp_ag_open_sco(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call) +{ + bool create_sco; + bt_hfp_call_state_t call_state; + + if (atomic_test_bit(ag->flags, BT_HFP_AG_CREATING_SCO)) { + LOG_WRN("SCO connection is creating!"); + return 0; + } + + hfp_ag_lock(ag); + create_sco = (ag->sco_chan.sco == NULL) ? true : false; + if (create_sco) { + atomic_set_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + } + hfp_ag_unlock(ag); + + if (create_sco) { + struct bt_conn *sco_conn = bt_hfp_ag_create_sco(ag); + + atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + atomic_clear_bit(ag->flags, BT_HFP_AG_AUDIO_CONN); + + if (sco_conn == NULL) { + LOG_ERR("Fail to create sco connection!"); + return -ENOTCONN; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_AUDIO_CONN, call == NULL); + if (call) { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_OPEN_SCO); + } + + LOG_DBG("SCO connection created (%p)", sco_conn); + } else { + if (call) { + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state == BT_HFP_CALL_INCOMING) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, true); + } + } + } + + return 0; +} + +static int bt_hfp_ag_codec_select(struct bt_hfp_ag *ag) +{ + int err; + + LOG_DBG(""); + + hfp_ag_lock(ag); + if (ag->selected_codec_id == 0) { + LOG_WRN("Codec is invalid, set default value"); + ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; + } + + if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) { + LOG_ERR("Codec is unsupported (codec id %d)", ag->selected_codec_id); + hfp_ag_unlock(ag); + return -EINVAL; + } + hfp_ag_unlock(ag); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BCS:%d\r\n", ag->selected_codec_id); + if (err != 0) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; +} + +static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call) +{ + int err; + uint32_t hf_codec_ids; + + hfp_ag_lock(ag); + hf_codec_ids = ag->hf_codec_ids; + hfp_ag_unlock(ag); + + if ((hf_codec_ids != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED)) { + err = bt_hfp_ag_codec_select(ag); + atomic_set_bit_to(ag->flags, BT_HFP_AG_CODEC_CONN, err == 0); + if (call) { + atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_OPEN_SCO, err == 0); + } + } else { + err = hfp_ag_open_sco(ag, call); + } + + return err; +} + +static void bt_hfp_ag_audio_connection(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err) { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static void bt_hfp_ag_call_setup_none_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, + BT_HFP_CALL_HELD_ACTIVE_HELD, bt_hfp_ag_accept_cb, + call); + if (err) { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } + return; + } + + bt_hfp_ag_audio_connection(ag, user_data); +} + +static void bt_hfp_ag_unit_call_accept(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, user_data); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_call_setup_none_cb, user_data); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +static int bt_hfp_ag_ata_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + int call_count; + struct bt_hfp_ag_call *call; + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + call_count = get_none_released_calls(ag); + if (call_count != 1) { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING, false); + __ASSERT(call, "Invalid call object"); + + if (call->call_state != BT_HFP_CALL_ALERTING) { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + hfp_ag_unlock(ag); + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + return -ENOTSUP; + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_accept, call); + + return err; +} + +static int bt_hfp_ag_cops_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t number; + + if (is_char(buf, '=')) { + static const uint32_t command_line_prefix[] = {3, 0}; + + for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) { + err = get_number(buf, &number); + if (err != 0) { + return -ENOTSUP; + } + + if (command_line_prefix[i] != number) { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) { + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + } + } + + atomic_set_bit(ag->flags, BT_HFP_AG_COPS_SET); + return 0; + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_COPS_SET)) { + return -ENOTSUP; + } + + if (!is_char(buf, '?')) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+COPS:%d,%d,\"%s\"\r\n", ag->mode, 0, + ag->operator); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; +} + +static int bt_hfp_ag_bcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ +#if BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE + int err; + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + if (ag->selected_codec_id && (!(ag->hf_codec_ids & BIT(ag->selected_codec_id)))) { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + + if (ag->sco_chan.sco) { + hfp_ag_unlock(ag); + return -ECONNREFUSED; + } + hfp_ag_unlock(ag); + + if (bt_ag && bt_ag->audio_connect_req) { + bt_ag->audio_connect_req(ag); + return 0; + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_audio_connection, NULL); + + return err; +#else + return -ENOTSUP; +#endif /* BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE */ +} + +static void bt_hfp_ag_unit_codec_conn_setup(struct bt_hfp_ag *ag, void *user_data) +{ + int err = hfp_ag_open_sco(ag, user_data); + + if (err) { + bt_hfp_ag_call_reject(ag, user_data); + } +} + +static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t number; + bool codec_conn; + struct bt_hfp_ag_call *call; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &number); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { + return -ESRCH; + } + + hfp_ag_lock(ag); + if (ag->selected_codec_id != number) { + LOG_ERR("Received codec id %d is not aligned with selected %d", number, + ag->selected_codec_id); + err = -ENOTSUP; + } else if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) { + LOG_ERR("Selected codec id %d is unsupported %d", ag->selected_codec_id, + ag->hf_codec_ids); + err = -ENOTSUP; + } + hfp_ag_unlock(ag); + + codec_conn = atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN); + atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO, false); + + if (err == 0) { + if (codec_conn && bt_ag && bt_ag->codec_negotiate) { + bt_ag->codec_negotiate(ag, err); + } + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_codec_conn_setup, call); + } else { + bt_hfp_call_state_t call_state; + + if (codec_conn && bt_ag && bt_ag->codec_negotiate) { + bt_ag->codec_negotiate(ag, err); + } + + if (call) { + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + if (call_state != BT_HFP_CALL_TERMINATE) { + (void)hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, call); + } + } + } + + return err; +} + +static void bt_hfp_ag_outgoing_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_OUTGOING); + + if (bt_ag && bt_ag->outgoing) { + bt_ag->outgoing(ag, call, call->number); + } + + if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING) || + atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { + int err; + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err) { + bt_hfp_ag_call_reject(ag, user_data); + } + } +} + +static void bt_hfp_ag_unit_call_outgoing(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_OUTGOING, + bt_hfp_ag_outgoing_cb, call); + + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static void bt_hfp_ag_call_held_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } + + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + + if ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + if (bt_ag && bt_ag->held) { + bt_ag->held(call); + } + } + } + + bt_hfp_ag_unit_call_outgoing(ag, user_data); +} + +static void bt_hfp_ag_unit_call_outgoing_3way(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + int err; + uint8_t old_value; + + atomic_set_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY); + + hfp_ag_lock(ag); + old_value = ag->indicator_value[BT_HFP_AG_CALL_HELD_IND]; + hfp_ag_unlock(ag); + if (old_value == BT_HFP_CALL_HELD_HELD) { + LOG_WRN("No active call"); + bt_hfp_ag_call_held_cb(ag, user_data); + return; + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, BT_HFP_CALL_HELD_HELD, + bt_hfp_ag_call_held_cb, call); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +static int bt_hfp_ag_outgoing_call(struct bt_hfp_ag *ag, const char *number, uint8_t type) +{ + int err; + size_t len; + struct bt_hfp_ag_call *call; + int call_count; + + len = strlen(number); + if (len == 0) { + return -ENOTSUP; + } + + if (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) { + return -ENAMETOOLONG; + } + + hfp_ag_lock(ag); + (void)strcpy(ag->last_number, number); + ag->type = type; + hfp_ag_unlock(ag); + + call = get_call_from_number(ag, number, type); + if (call) { + return -EBUSY; + } + + call_count = get_none_released_calls(ag); + + if (call_count) { +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) { + return -ENOEXEC; + } + + if (!get_active_held_calls(ag)) { + LOG_WRN("The first call is not accepted"); + return -EBUSY; + } +#else + return -EBUSY; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + } + + call = get_new_call(ag, number, type); + if (!call) { + LOG_WRN("Cannot allocate a new call object"); + return -ENOMEM; + } + + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_OUTGOING); + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (call_count) { + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing_3way, call); + if (err) { + free_call(call); + } + return err; + } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, call); + if (err) { + free_call(call); + } + return err; +} + +static int bt_hfp_ag_atd_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + char *number = NULL; + bool is_memory_dial = false; + + if (buf->data[buf->len - 1] != '\r') { + return -ENOTSUP; + } + + if (is_char(buf, '>')) { + is_memory_dial = true; + } + + if ((buf->len - 1) > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) { + return -ENAMETOOLONG; + } + + buf->data[buf->len - 1] = '\0'; + + if (is_memory_dial) { + if (bt_ag && bt_ag->memory_dial) { + err = bt_ag->memory_dial(ag, &buf->data[0], &number); + if ((err != 0) || (number == NULL)) { + return -ENOTSUP; + } + } else { + return -ENOTSUP; + } + } else { + number = &buf->data[0]; + if (bt_ag && bt_ag->number_call) { + err = bt_ag->number_call(ag, &buf->data[0]); + if (err) { + return err; + } + } else { + return -ENOTSUP; + } + } + + return bt_hfp_ag_outgoing_call(ag, number, 0); +} + +static int bt_hfp_ag_bldn_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + return bt_hfp_ag_outgoing_call(ag, ag->last_number, ag->type); +} + +static int bt_hfp_ag_clip_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t clip; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &clip); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (clip > 1) { + return -ENOTSUP; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_CLIP_ENABLE, clip == 1); + + return err; +} + +static int bt_hfp_ag_vgm_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t vgm; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &vgm); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (vgm > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid vgm (%d>%d)", vgm, BT_HFP_HF_VGM_GAIN_MAX); + return -ENOTSUP; + } + + if (bt_ag && bt_ag->vgm) { + bt_ag->vgm(ag, (uint8_t)vgm); + } + return err; +} + +static int bt_hfp_ag_vgs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t vgs; - if (!is_char(buf, '?')) { + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &vgs); + if (err != 0) { return -ENOTSUP; } @@ -518,82 +2694,381 @@ static int bt_hfp_ag_cind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - if (!inquiry_status) { - err = hfp_ag_send_data( - ag, NULL, NULL, - "\r\n+CIND:(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%" - "d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d))\r\n", - ag_ind[BT_HFP_AG_SERVICE_IND].name, ag_ind[BT_HFP_AG_SERVICE_IND].min, - ag_ind[BT_HFP_AG_SERVICE_IND].connector, ag_ind[BT_HFP_AG_SERVICE_IND].max, - ag_ind[BT_HFP_AG_CALL_IND].name, ag_ind[BT_HFP_AG_CALL_IND].min, - ag_ind[BT_HFP_AG_CALL_IND].connector, ag_ind[BT_HFP_AG_CALL_IND].max, - ag_ind[BT_HFP_AG_CALL_SETUP_IND].name, ag_ind[BT_HFP_AG_CALL_SETUP_IND].min, - ag_ind[BT_HFP_AG_CALL_SETUP_IND].connector, - ag_ind[BT_HFP_AG_CALL_SETUP_IND].max, ag_ind[BT_HFP_AG_CALL_HELD_IND].name, - ag_ind[BT_HFP_AG_CALL_HELD_IND].min, - ag_ind[BT_HFP_AG_CALL_HELD_IND].connector, - ag_ind[BT_HFP_AG_CALL_HELD_IND].max, ag_ind[BT_HFP_AG_SIGNAL_IND].name, - ag_ind[BT_HFP_AG_SIGNAL_IND].min, ag_ind[BT_HFP_AG_SIGNAL_IND].connector, - ag_ind[BT_HFP_AG_SIGNAL_IND].max, ag_ind[BT_HFP_AG_ROAM_IND].name, - ag_ind[BT_HFP_AG_ROAM_IND].min, ag_ind[BT_HFP_AG_ROAM_IND].connector, - ag_ind[BT_HFP_AG_ROAM_IND].max, ag_ind[BT_HFP_AG_BATTERY_IND].name, - ag_ind[BT_HFP_AG_BATTERY_IND].min, ag_ind[BT_HFP_AG_BATTERY_IND].connector, - ag_ind[BT_HFP_AG_BATTERY_IND].max); - } else { - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%d,%d,%d,%d,%d,%d,%d\r\n", - ag->indicator_value[BT_HFP_AG_SERVICE_IND], - ag->indicator_value[BT_HFP_AG_CALL_IND], - ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND], - ag->indicator_value[BT_HFP_AG_CALL_HELD_IND], - ag->indicator_value[BT_HFP_AG_SIGNAL_IND], - ag->indicator_value[BT_HFP_AG_ROAM_IND], - ag->indicator_value[BT_HFP_AG_BATTERY_IND]); + if (vgs > BT_HFP_HF_VGS_GAIN_MAX) { + LOG_ERR("Invalid vgs (%d>%d)", vgs, BT_HFP_HF_VGS_GAIN_MAX); + return -ENOTSUP; } + if (bt_ag && bt_ag->vgs) { + bt_ag->vgs(ag, (uint8_t)vgs); + } return err; } -static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data) +static int bt_hfp_ag_nrec_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { - bool is_inband_ringtone; + int err; + uint32_t disable; - hfp_ag_lock(ag); - is_inband_ringtone = (ag->ag_features & BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false; - hfp_ag_unlock(ag); + if (!is_char(buf, '=')) { + return -ENOTSUP; + } - if (is_inband_ringtone) { - int err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR:1\r\n"); + err = get_number(buf, &disable); + if (err != 0) { + return -ENOTSUP; + } - atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, err == 0); + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_ECNR)) { + return -ENOTSUP; + } + + if (disable) { + LOG_ERR("Only support EC NR disable"); + return -ENOTSUP; + } + +#if defined(CONFIG_BT_HFP_AG_ECNR) + if (bt_ag && bt_ag->ecnr_turn_off) { + bt_ag->ecnr_turn_off(ag); + return 0; } +#endif /* CONFIG_BT_HFP_AG_ECNR */ + return -ENOTSUP; } -static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static void btrh_held_call_updated_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); +} + +static void btrh_held_call_setup_updated_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (bt_ag && bt_ag->incoming_held) { + bt_ag->incoming_held(call); + } +} + +static void btrh_held_cb(struct bt_hfp_ag *ag, void *user_data) { - uint32_t number; int err; - static const uint32_t command_line_prefix[] = {3, 0, 0}; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, btrh_held_call_updated_cb, + user_data); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + bt_hfp_ag_unit_call_terminate(ag, user_data); + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + btrh_held_call_setup_updated_cb, user_data); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static void btrh_accept_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (bt_ag && bt_ag->accept) { + bt_ag->accept(call); + } + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err) { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static void btrh_reject_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_reject_cb, user_data); + if (err) { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static int bt_hfp_ag_btrh_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t action; + bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call; + + if (is_char(buf, '?')) { + call = get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD, false); + hfp_ag_lock(ag); + call_state = call->call_state; + hfp_ag_unlock(ag); + if (call && (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + return 0; + } if (!is_char(buf, '=')) { return -ENOTSUP; } - for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) { - err = get_number(buf, &number); - if (err != 0) { - return -ENOTSUP; + err = get_number(buf, &action); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (action == BT_HFP_BTRH_ON_HOLD) { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING, + BT_HFP_CALL_ALERTING | BT_HFP_CALL_INCOMING); + if (call) { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_held_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + } else if (action == BT_HFP_BTRH_ACCEPTED) { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING_HELD, + BT_HFP_CALL_ACTIVE); + if (call) { + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_accept_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ACCEPTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } + } else if (action == BT_HFP_BTRH_REJECTED) { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING_HELD, + BT_HFP_CALL_ACTIVE); + if (call) { + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; } + } - if (!is_char(buf, ',')) { - return -ENOTSUP; + return -ENOTSUP; +} + +static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { + return -ENOEXEC; + } + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (value > 1) { + return -ENOTSUP; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_CCWA_ENABLE, value); + return 0; +} + +static void bt_hfp_ag_vr_activate(struct bt_hfp_ag *ag, void *user_data) +{ + bool feature; + + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + if (bt_ag && bt_ag->voice_recognition) { + bt_ag->voice_recognition(ag, true); + } else { + (void)bt_hfp_ag_audio_connect(ag, BT_HFP_AG_CODEC_CVSD); + } +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + + atomic_set_bit_to(ag->flags, BT_HFP_AG_VRE_R2A, !feature); + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { + if (bt_ag && bt_ag->ready_to_accept_audio) { + bt_ag->ready_to_accept_audio(ag); } + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} - if (command_line_prefix[i] != number) { +static void bt_hfp_ag_vr_deactivate(struct bt_hfp_ag *ag, void *user_data) +{ +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (bt_ag && bt_ag->voice_recognition) { + bt_ag->voice_recognition(ag, false); + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +static void bt_hfp_ag_vr_ready2accept(struct bt_hfp_ag *ag, void *user_data) +{ + atomic_set_bit(ag->flags, BT_HFP_AG_VRE_R2A); + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (bt_ag && bt_ag->ready_to_accept_audio) { + bt_ag->ready_to_accept_audio(ag); + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOICE_RECG, BT_HFP_AG_FEATURE_VOICE_RECG)) { + return -ENOEXEC; + } + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + switch (value) { + case BT_HFP_BVRA_DEACTIVATION: + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("VR is not activated"); + return -ENOTSUP; + } + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_deactivate, NULL); + break; + case BT_HFP_BVRA_ACTIVATION: + if (atomic_test_and_set_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("VR has been activated"); + return -ENOTSUP; + } + atomic_clear_bit(ag->flags, BT_HFP_AG_VRE_R2A); + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_activate, NULL); + break; + case BT_HFP_BVRA_READY_TO_ACCEPT: + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG)) { + LOG_WRN("Enhance voice recognition is not supported"); + return -ENOEXEC; + } + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("Voice recognition is not activated"); return -ENOTSUP; } + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_ready2accept, NULL); + break; + default: + return -ENOTSUP; } - err = get_number(buf, &number); + return err; +} + +static int bt_hfp_ag_binp_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) + char *number = NULL; +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_VOICE_TAG)) { + return -ENOEXEC; + } + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) { + return -ENOTSUP; + } + + if (value != 1) { + return -ENOTSUP; + } + +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) + if (bt_ag && bt_ag->request_phone_number) { + err = bt_ag->request_phone_number(ag, &number); + if (err) { + LOG_DBG("Cannot request phone number :(%d)", err); + return err; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BINP:\"%s\"\r\n", number); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; + } +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + + return -ENOTSUP; +} + +static int bt_hfp_ag_vts_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + char code; + + if (!is_char(buf, '=')) { + return -ENOTSUP; + } + + err = get_char(buf, &code); if (err != 0) { return -ENOTSUP; } @@ -602,13 +3077,18 @@ static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - if (number == 1) { - atomic_set_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); - bt_hfp_ag_set_state(ag, BT_HFP_CONNECTED); - err = hfp_ag_next_step(ag, bt_hfp_ag_set_in_band_ring, NULL); - return err; - } else if (number == 0) { - atomic_clear_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); + if (!IS_VALID_DTMF(code)) { + LOG_ERR("Invalid code"); + return -EINVAL; + } + + if (!get_active_calls(ag)) { + LOG_ERR("Not valid ongoing call"); + return -ENOTSUP; + } + + if (bt_ag && bt_ag->transmit_dtmf_code) { + bt_ag->transmit_dtmf_code(ag, code); } else { return -ENOTSUP; } @@ -616,129 +3096,53 @@ static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return 0; } -static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ - return -EOPNOTSUPP; -} - -static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static int send_subscriber_number(struct bt_hfp_ag *ag, char *number, + uint8_t type, uint8_t service) { - uint32_t indicator; - uint32_t hf_indicators = 0U; int err; - char *data; - uint32_t len; - hfp_ag_lock(ag); - if (!((ag->ag_features & BT_HFP_AG_FEATURE_HF_IND) && - (ag->hf_features & BT_HFP_HF_FEATURE_HF_IND))) { - hfp_ag_unlock(ag); - return -EOPNOTSUPP; + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CNUM:,\"%s\",%d,,%d\r\n", + number, type, service); + if (err) { + LOG_ERR("Fail to send subscriber number :(%d)", err); } - hfp_ag_unlock(ag); - - if (is_char(buf, '?')) { - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } + return err; +} - hfp_ag_lock(ag); - hf_indicators = ag->hf_indicators_of_hf & ag->hf_indicators_of_ag; - hfp_ag_unlock(ag); - len = (sizeof(hf_indicators) * 8) > HFP_HF_IND_MAX ? HFP_HF_IND_MAX - : (sizeof(hf_indicators) * 8); - for (int i = 1; i < len; i++) { - if (BIT(i) & hf_indicators) { - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", i, 1); - } else { - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", i, 0); - } - if (err < 0) { - return err; - } - if (hf_indicators == 0) { - break; - } - } - return 0; - } +static int bt_hfp_ag_cnum_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; - if (!is_char(buf, '=')) { + if (!is_char(buf, '\r')) { return -ENOTSUP; } - if (is_char(buf, '?')) { - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } - - data = &ag->buffer[0]; - *data = '('; - data++; - hfp_ag_lock(ag); - hf_indicators = ag->hf_indicators_of_ag; - hfp_ag_unlock(ag); - len = (sizeof(hf_indicators) * 8) > HFP_HF_IND_MAX ? HFP_HF_IND_MAX - : (sizeof(hf_indicators) * 8); - for (int i = 1; (i < len) && (hf_indicators != 0); i++) { - if (BIT(i) & hf_indicators) { - int length = snprintk( - data, (char *)&ag->buffer[HF_MAX_BUF_LEN - 1] - data - 3, - "%d", i); - data += length; - hf_indicators &= ~BIT(i); - } - if (hf_indicators != 0) { - *data = ','; - data++; - } - } - *data = ')'; - data++; - *data = '\r'; - data++; - *data = '\0'; - data++; - - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%s\r\n", &ag->buffer[0]); + if (bt_ag && bt_ag->subscriber_number) { + err = bt_ag->subscriber_number(ag, send_subscriber_number); return err; } - while (buf->len > 0) { - err = get_number(buf, &indicator); - if (err != 0) { - return -ENOTSUP; - } - - if (!is_char(buf, ',')) { - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } - } - - if (indicator < (sizeof(hf_indicators) * 8)) { - hf_indicators |= BIT(indicator); - } - } - - hfp_ag_lock(ag); - ag->hf_indicators_of_hf = hf_indicators; - hfp_ag_unlock(ag); - return 0; } -static int bt_hfp_ag_cmee_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static int bt_hfp_ag_biev_handler(struct bt_hfp_ag *ag, struct net_buf *buf) { - uint32_t cmee; - int err; + uint32_t indicator; + uint32_t value; if (!is_char(buf, '=')) { return -ENOTSUP; } - err = get_number(buf, &cmee); - if (err != 0) { + if (get_number(buf, &indicator)) { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) { + return -ENOTSUP; + } + + if (get_number(buf, &value)) { return -ENOTSUP; } @@ -746,1096 +3150,1275 @@ static int bt_hfp_ag_cmee_handler(struct bt_hfp_ag *ag, struct net_buf *buf) return -ENOTSUP; } - if (cmee > 1) { + hfp_ag_lock(ag); + if (!(ag->hf_indicators_of_ag & BIT(indicator))) { + hfp_ag_unlock(ag); return -ENOTSUP; } + hfp_ag_unlock(ag); - atomic_set_bit_to(ag->flags, BT_HFP_AG_CMEE_ENABLE, cmee == 1); +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) + if (bt_ag && bt_ag->hf_indicator_value) { + bt_ag->hf_indicator_value(ag, indicator, value); + } +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ return 0; } -static int hfp_ag_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, - uint8_t value, bt_hfp_ag_tx_cb_t cb, void *user_data) +static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { + {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, + {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, + {"AT+CHLD", bt_hfp_ag_chld_handler}, {"AT+BIND", bt_hfp_ag_bind_handler}, + {"AT+CMEE", bt_hfp_ag_cmee_handler}, {"AT+CHUP", bt_hfp_ag_chup_handler}, + {"AT+CLCC", bt_hfp_ag_clcc_handler}, {"AT+BIA", bt_hfp_ag_bia_handler}, + {"ATA", bt_hfp_ag_ata_handler}, {"AT+COPS", bt_hfp_ag_cops_handler}, + {"AT+BCC", bt_hfp_ag_bcc_handler}, {"AT+BCS", bt_hfp_ag_bcs_handler}, + {"ATD", bt_hfp_ag_atd_handler}, {"AT+BLDN", bt_hfp_ag_bldn_handler}, + {"AT+CLIP", bt_hfp_ag_clip_handler}, {"AT+VGM", bt_hfp_ag_vgm_handler}, + {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, + {"AT+BTRH", bt_hfp_ag_btrh_handler}, {"AT+CCWA", bt_hfp_ag_ccwa_handler}, + {"AT+BVRA", bt_hfp_ag_bvra_handler}, {"AT+BINP", bt_hfp_ag_binp_handler}, + {"AT+VTS", bt_hfp_ag_vts_handler}, {"AT+CNUM", bt_hfp_ag_cnum_handler}, + {"AT+BIEV", bt_hfp_ag_biev_handler}, +}; + +static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) { - int err; - uint8_t old_value; + struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + + bt_hfp_ag_set_state(ag, BT_HFP_CONFIG); + + LOG_DBG("AG %p", ag); +} + +static void hfp_ag_disconnected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + bt_hfp_call_state_t call_state; + sys_snode_t *node; + struct bt_ag_tx *tx; + struct bt_hfp_ag_call *call; + + k_work_cancel_delayable(&ag->tx_work); hfp_ag_lock(ag); - old_value = ag->indicator_value[index]; - if (value == old_value) { - LOG_ERR("Duplicate value setting, indicator %d, old %d -> new %d", index, old_value, - value); + node = sys_slist_get(&ag->tx_pending); + hfp_ag_unlock(ag); + tx = CONTAINER_OF(node, struct bt_ag_tx, node); + while (tx) { + if (tx->buf && !atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { + net_buf_unref(tx->buf); + } + tx->err = -ESHUTDOWN; + k_fifo_put(&ag_tx_notify, tx); + hfp_ag_lock(ag); + node = sys_slist_get(&ag->tx_pending); hfp_ag_unlock(ag); - return -EINVAL; + tx = CONTAINER_OF(node, struct bt_ag_tx, node); } - ag->indicator_value[index] = value; - hfp_ag_unlock(ag); + bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTED); - LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { + continue; + } - err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", (uint8_t)index + 1, value); - if (err) { hfp_ag_lock(ag); - ag->indicator_value[index] = old_value; + call_state = call->call_state; hfp_ag_unlock(ag); - LOG_ERR("Fail to update indicator %d, current %d", index, old_value); + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_ACTIVE) || + (call_state == BT_HFP_CALL_HOLD)) { + bt_hfp_ag_terminate_cb(ag, call); + } } - return err; + ag->acl_conn = NULL; + + LOG_DBG("AG %p", ag); } -static void hfp_ag_close_sco(struct bt_hfp_ag *ag) +static void hfp_ag_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) { - struct bt_conn *sco; + struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + uint8_t *data = buf->data; + uint16_t len = buf->len; + enum at_cme cme_err; + int err = -ENOEXEC; - LOG_DBG(""); + LOG_HEXDUMP_DBG(data, len, "Received:"); - hfp_ag_lock(ag); - sco = ag->sco_chan.sco; - ag->sco_chan.sco = NULL; - hfp_ag_unlock(ag); - if (sco != NULL) { - LOG_DBG("Disconnect sco %p", sco); - bt_conn_disconnect(sco, BT_HCI_ERR_LOCALHOST_TERM_CONN); + for (uint32_t index = 0; index < ARRAY_SIZE(cmd_handlers); index++) { + if (strlen(cmd_handlers[index].cmd) > len) { + continue; + } + if (strncmp((char *)data, cmd_handlers[index].cmd, + strlen(cmd_handlers[index].cmd)) != 0) { + continue; + } + if (NULL != cmd_handlers[index].handler) { + (void)net_buf_pull(buf, strlen(cmd_handlers[index].cmd)); + err = cmd_handlers[index].handler(ag, buf); + LOG_DBG("AT commander is handled (err %d)", err); + break; + } } -} -static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data) -{ - hfp_ag_close_sco(ag); - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_TERMINATE); + if ((err != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CMEE_ENABLE)) { + cme_err = bt_hfp_ag_get_cme_err(err); + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CME ERROR:%d\r\n", (uint32_t)cme_err); + } else { + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n%s\r\n", (err == 0) ? "OK" : "ERROR"); + } - if (bt_ag && bt_ag->reject) { - bt_ag->reject(ag); + if (err != 0) { + LOG_ERR("HFP AG send response err :(%d)", err); } } -static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data) +static void bt_hfp_ag_thread(void *p1, void *p2, void *p3) { - int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_reject_cb, user_data); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + struct bt_ag_tx *tx; + bt_hfp_ag_tx_cb_t cb; + struct bt_hfp_ag *ag; + void *user_data; + bt_hfp_state_t state; + int err; + + while (true) { + tx = (struct bt_ag_tx *)k_fifo_get(&ag_tx_notify, K_FOREVER); + + if (tx == NULL) { + continue; + } + + cb = tx->cb; + ag = tx->ag; + user_data = tx->user_data; + err = tx->err; + + bt_ag_tx_free(tx); + + if (err < 0) { + hfp_ag_lock(ag); + state = ag->state; + hfp_ag_unlock(ag); + if ((state != BT_HFP_DISCONNECTED) && (state != BT_HFP_DISCONNECTING)) { + bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTING); + bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); + } + } + + if (cb) { + cb(ag, user_data); + } } } -static void bt_hfp_ag_terminate_cb(struct bt_hfp_ag *ag, void *user_data) +static void hfp_ag_sent(struct bt_rfcomm_dlc *dlc, int err) { - hfp_ag_close_sco(ag); - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_TERMINATE); + struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + sys_snode_t *node; + struct bt_ag_tx *tx; - if (bt_ag && bt_ag->terminate) { - bt_ag->terminate(ag); + hfp_ag_lock(ag); + /* Clear the tx ongoing flag */ + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { + LOG_WRN("tx ongoing flag is not set"); + hfp_ag_unlock(ag); + return; } -} -static void bt_hfp_ag_unit_call_terminate(struct bt_hfp_ag *ag, void *user_data) -{ - int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, - user_data); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + node = sys_slist_get(&ag->tx_pending); + hfp_ag_unlock(ag); + if (!node) { + LOG_ERR("No pending tx"); + return; } -} - -static int bt_hfp_ag_chup_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ - int err; - bt_hfp_call_state_t call_state; - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } + tx = CONTAINER_OF(node, struct bt_ag_tx, node); + LOG_DBG("Completed pending tx %p", tx); - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); + /* Restart the tx work */ + k_work_reschedule(&ag->tx_work, K_NO_WAIT); - if (call_state == BT_HFP_CALL_ALERTING) { - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - return -ENOTSUP; - } - err = hfp_ag_next_step(ag, bt_hfp_ag_call_reject, NULL); - } else if ((call_state == BT_HFP_CALL_ACTIVE) || (call_state == BT_HFP_CALL_HOLD)) { - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, NULL); - } else { - return -ENOTSUP; - } - return err; + tx->err = err; + k_fifo_put(&ag_tx_notify, tx); } -static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag *ag) +static const char *bt_ag_get_call_state_string(bt_hfp_call_state_t call_state) { - uint8_t status = HFP_AG_CLCC_STATUS_INVALID; + const char *s; - hfp_ag_lock(ag); - switch (ag->call_state) { + switch (call_state) { case BT_HFP_CALL_TERMINATE: + s = "terminate"; + break; + case BT_HFP_CALL_ACTIVE: + s = "active"; + break; + case BT_HFP_CALL_HOLD: + s = "hold"; break; case BT_HFP_CALL_OUTGOING: - status = HFP_AG_CLCC_STATUS_DIALING; + s = "outgoing"; break; case BT_HFP_CALL_INCOMING: - status = HFP_AG_CLCC_STATUS_INCOMING; + s = "incoming"; break; case BT_HFP_CALL_ALERTING: - if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - status = HFP_AG_CLCC_STATUS_WAITING; - } else { - status = HFP_AG_CLCC_STATUS_ALERTING; - } - break; - case BT_HFP_CALL_ACTIVE: - status = HFP_AG_CLCC_STATUS_ACTIVE; - break; - case BT_HFP_CALL_HOLD: - status = HFP_AG_CLCC_STATUS_HELD; + s = "alerting"; break; default: + s = "unknown"; break; } - hfp_ag_unlock(ag); - return status; + return s; } -static int bt_hfp_ag_clcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data) { int err; - uint8_t dir; - uint8_t status; - uint8_t mode; - uint8_t mpty; + bt_hfp_call_state_t call_state; + uint8_t call_setup_ind; + uint8_t call_ind; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } + LOG_DBG(""); hfp_ag_lock(ag); - if (ag->call_state == BT_HFP_CALL_TERMINATE) { - /* AG shall always send OK response to HF */ - hfp_ag_unlock(ag); - return 0; - } + call_state = call->call_state; + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; hfp_ag_unlock(ag); - dir = atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL) ? 1 : 0; - status = bt_hfp_get_call_state(ag); - mode = 0; - mpty = 0; - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLCC:%d,%d,%d,%d,%d\r\n", 1, dir, status, mode, - mpty); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + switch (call_state) { + case BT_HFP_CALL_TERMINATE: + break; + case BT_HFP_CALL_ACTIVE: + break; + case BT_HFP_CALL_HOLD: + break; + case BT_HFP_CALL_OUTGOING: + case BT_HFP_CALL_INCOMING: + case BT_HFP_CALL_ALERTING: + default: + LOG_WRN("Call timeout, status %s", bt_ag_get_call_state_string(call_state)); + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY) || + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { + if (!call_setup_ind) { + return; + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, call); + if (err) { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + return; + } + + if (call_setup_ind && call_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, NULL, NULL); + if (err) { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } else { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, + bt_hfp_ag_terminate_cb, call); + if (err) { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + } + } else if (call_setup_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, bt_hfp_ag_reject_cb, + call); + if (err) { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + } else if (call_ind) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, + bt_hfp_ag_terminate_cb, call); + if (err) { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + } + break; } +} - /* AG shall always send OK response to HF */ - return 0; +static void bt_ag_deferred_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_ag_call *call = CONTAINER_OF(dwork, struct bt_hfp_ag_call, deferred_work); + struct bt_hfp_ag *ag = call->ag; + + (void)hfp_ag_next_step(ag, bt_ag_deferred_work_cb, call); } -static int bt_hfp_ag_bia_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +static void bt_ag_ringing_work_cb(struct bt_hfp_ag *ag, void *user_data) { - uint32_t number; int err; - int index = 0; - uint32_t indicator; + bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; - if (!is_char(buf, '=')) { - return -ENOTSUP; - } + LOG_DBG(""); hfp_ag_lock(ag); - indicator = ag->indicator; + call_state = call->call_state; hfp_ag_unlock(ag); + if (call_state == BT_HFP_CALL_ALERTING) { - while (buf->len > 0) { - err = get_number(buf, &number); - if (err == 0) { - /* Valid number */ - if (number) { - indicator |= BIT(index); - } else { - indicator &= ~BIT(index); - } + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + return; } - if (is_char(buf, ',')) { - index++; + k_work_reschedule(&call->ringing_work, + K_SECONDS(CONFIG_BT_HFP_AG_RING_NOTIFY_INTERVAL)); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\nRING\r\n"); + if (err) { + LOG_ERR("Fail to send RING %d", err); } else { - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } - if (buf->len != 0) { - return -ENOTSUP; + if (atomic_test_bit(ag->flags, BT_HFP_AG_CLIP_ENABLE)) { + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLIP:\"%s\",%d\r\n", + call->number, 0); + if (err) { + LOG_ERR("Fail to send CLIP %d", err); + } } } } +} - /* Force call, call setup and held call indicators are enabled. */ - indicator = BIT(BT_HFP_AG_CALL_IND) | BIT(BT_HFP_AG_CALL_SETUP_IND) | - BIT(BT_HFP_AG_CALL_HELD_IND); - - hfp_ag_lock(ag); - ag->indicator = indicator; - hfp_ag_unlock(ag); +static void bt_ag_ringing_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_ag_call *call = CONTAINER_OF(dwork, struct bt_hfp_ag_call, ringing_work); - return 0; + (void)hfp_ag_next_step(call->ag, bt_ag_ringing_work_cb, call); } -static void bt_hfp_ag_accept_cb(struct bt_hfp_ag *ag, void *user_data) +static K_KERNEL_STACK_MEMBER(ag_thread_stack, CONFIG_BT_HFP_AG_THREAD_STACK_SIZE); + +static struct bt_hfp_ag *hfp_ag_create(struct bt_conn *conn) { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ACTIVE); + static struct bt_rfcomm_dlc_ops ops = { + .connected = hfp_ag_connected, + .disconnected = hfp_ag_disconnected, + .recv = hfp_ag_recv, + .sent = hfp_ag_sent, + }; + static k_tid_t ag_thread_id; + static struct k_thread ag_thread; + size_t index; + struct bt_hfp_ag *ag; - if (bt_ag && bt_ag->accept) { - bt_ag->accept(ag); + LOG_DBG("conn %p", conn); + + if (ag_thread_id == NULL) { + + k_fifo_init(&ag_tx_free); + k_fifo_init(&ag_tx_notify); + + for (index = 0; index < ARRAY_SIZE(ag_tx); index++) { + k_fifo_put(&ag_tx_free, &ag_tx[index]); + } + + ag_thread_id = k_thread_create( + &ag_thread, ag_thread_stack, K_KERNEL_STACK_SIZEOF(ag_thread_stack), + bt_hfp_ag_thread, NULL, NULL, NULL, + K_PRIO_COOP(CONFIG_BT_HFP_AG_THREAD_PRIO), 0, K_NO_WAIT); + __ASSERT(ag_thread_id, "Cannot create thread for AG"); + k_thread_name_set(ag_thread_id, "HFP AG"); } -} -static void bt_hfp_ag_call_ringing_cb(struct bt_hfp_ag *ag, bool in_bond) -{ - if (bt_ag && bt_ag->ringing) { - bt_ag->ringing(ag, in_bond); + index = (size_t)bt_conn_index(conn); + ag = &bt_hfp_ag_pool[index]; + if (ag->acl_conn) { + LOG_ERR("AG connection (%p) is established", conn); + return NULL; + } + + (void)memset(ag, 0, sizeof(struct bt_hfp_ag)); + + sys_slist_init(&ag->tx_pending); + + k_sem_init(&ag->lock, 1, 1); + + ag->rfcomm_dlc.ops = &ops; + ag->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; + + /* Set the supported features*/ + ag->ag_features = BT_HFP_AG_SUPPORTED_FEATURES; + + /* Support HF indicators */ + if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY)) { + ag->hf_indicators_of_ag |= BIT(HFP_HF_ENHANCED_SAFETY_IND); + } + + if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY)) { + ag->hf_indicators_of_ag |= BIT(HFP_HF_BATTERY_LEVEL_IND); + } + + ag->hf_indicators = ag->hf_indicators_of_ag; + + /* If supported codec ids cannot be notified, disable codec negotiation. */ + if (!(bt_ag && bt_ag->codec)) { + ag->ag_features &= ~BT_HFP_AG_FEATURE_CODEC_NEG; } + + ag->hf_features = 0; + ag->hf_codec_ids = 0; + + ag->acl_conn = conn; + + /* Set AG indicator value */ + ag->indicator_value[BT_HFP_AG_SERVICE_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = 0; + ag->indicator_value[BT_HFP_AG_SIGNAL_IND] = 0; + ag->indicator_value[BT_HFP_AG_ROAM_IND] = 0; + ag->indicator_value[BT_HFP_AG_BATTERY_IND] = 0; + + /* Set AG indicator status */ + ag->indicator = BIT(BT_HFP_AG_SERVICE_IND) | BIT(BT_HFP_AG_CALL_IND) | + BIT(BT_HFP_AG_CALL_SETUP_IND) | BIT(BT_HFP_AG_CALL_HELD_IND) | + BIT(BT_HFP_AG_SIGNAL_IND) | BIT(BT_HFP_AG_ROAM_IND) | + BIT(BT_HFP_AG_BATTERY_IND); + + /* Set AG operator */ + memcpy(ag->operator, "UNKNOWN", sizeof("UNKNOWN")); + + /* Set Codec ID*/ + ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; + + /* Init delay work */ + k_work_init_delayable(&ag->tx_work, bt_ag_tx_work); + + return ag; } -static void hfp_ag_sco_connected(struct bt_sco_chan *chan) +int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t channel) { - struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); - bt_hfp_call_state_t call_state; + struct bt_hfp_ag *new_ag; + int err; - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); - if (call_state == BT_HFP_CALL_INCOMING) { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ALERTING); - bt_hfp_ag_call_ringing_cb(ag, true); + LOG_DBG(""); + + if (!conn || !ag || !channel) { + return -EINVAL; } - if ((bt_ag) && bt_ag->sco_connected) { - bt_ag->sco_connected(ag, chan->sco); + if (!bt_ag) { + return -EFAULT; + } + + new_ag = hfp_ag_create(conn); + if (!new_ag) { + return -ECONNREFUSED; + } + + err = bt_rfcomm_dlc_connect(conn, &new_ag->rfcomm_dlc, channel); + if (err != 0) { + (void)memset(new_ag, 0, sizeof(*new_ag)); + *ag = NULL; + } else { + *ag = new_ag; + bt_hfp_ag_set_state(*ag, BT_HFP_CONNECTING); + } + + return err; +} + +int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag) +{ + LOG_DBG(""); + + if (ag == NULL) { + return -EINVAL; } + + bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTING); + + return bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); } -static void hfp_ag_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason) +static int hfp_ag_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) { - struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); - bt_hfp_call_state_t call_state; + struct bt_hfp_ag *ag; - if ((bt_ag) && bt_ag->sco_disconnected) { - bt_ag->sco_disconnected(ag); - } + ag = hfp_ag_create(conn); - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); - if ((call_state == BT_HFP_CALL_INCOMING) || (call_state == BT_HFP_CALL_OUTGOING)) { - bt_hfp_ag_call_reject(ag, NULL); + if (!ag) { + return -ECONNREFUSED; } + + *dlc = &ag->rfcomm_dlc; + + return 0; } -static struct bt_conn *bt_hfp_ag_create_sco(struct bt_hfp_ag *ag) +static int bt_hfp_ag_sco_accept(const struct bt_sco_accept_info *info, + struct bt_sco_chan **chan) { - struct bt_conn *sco_conn; - static struct bt_sco_chan_ops ops = { .connected = hfp_ag_sco_connected, .disconnected = hfp_ag_sco_disconnected, }; + size_t index; + struct bt_hfp_ag *ag; - LOG_DBG(""); + LOG_DBG("conn %p", info->acl); - if (ag->sco_chan.sco == NULL) { - ag->sco_chan.ops = &ops; + index = (size_t)bt_conn_index(info->acl); + ag = &bt_hfp_ag_pool[index]; + if (ag->acl_conn != info->acl) { + LOG_ERR("ACL %p of AG is unaligned with SCO's %p", ag->acl_conn, info->acl); + return -EINVAL; + } - /* create SCO connection*/ - sco_conn = bt_conn_create_sco(&ag->acl_conn->br.dst, &ag->sco_chan); - if (sco_conn != NULL) { - LOG_DBG("Created sco %p", sco_conn); - bt_conn_unref(sco_conn); - } - } else { - sco_conn = ag->sco_chan.sco; + if (ag->sco_chan.sco) { + return -ECONNREFUSED; } - return sco_conn; + ag->sco_chan.ops = &ops; + + *chan = &ag->sco_chan; + + return 0; } -static int hfp_ag_open_sco(struct bt_hfp_ag *ag) +static void hfp_ag_init(void) { - bool create_sco; + static struct bt_rfcomm_server chan = { + .channel = BT_RFCOMM_CHAN_HFP_AG, + .accept = hfp_ag_accept, + }; - if (atomic_test_bit(ag->flags, BT_HFP_AG_CREATING_SCO)) { - LOG_WRN("SCO connection is creating!"); - return 0; - } + bt_rfcomm_server_register(&chan); - hfp_ag_lock(ag); - create_sco = (ag->sco_chan.sco == NULL) ? true : false; - if (create_sco) { - atomic_set_bit(ag->flags, BT_HFP_AG_CREATING_SCO); - } - hfp_ag_unlock(ag); + static struct bt_sco_server sco_server = { + .sec_level = BT_SECURITY_L0, + .accept = bt_hfp_ag_sco_accept, + }; - if (create_sco) { - struct bt_conn *sco_conn = bt_hfp_ag_create_sco(ag); + bt_sco_server_register(&sco_server); - atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + bt_sdp_register_service(&hfp_ag_rec); +} - if (sco_conn == NULL) { - LOG_ERR("Fail to create sco connection!"); - return -ENOTCONN; - } +int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb) +{ + if (!cb) { + return -EINVAL; + } - LOG_DBG("SCO connection created (%p)", sco_conn); + if (bt_ag) { + return -EALREADY; } + bt_ag = cb; + + hfp_ag_init(); + return 0; } -static int bt_hfp_ag_codec_select(struct bt_hfp_ag *ag) +static void bt_hfp_ag_incoming_cb(struct bt_hfp_ag *ag, void *user_data) { - int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; - LOG_DBG(""); + __ASSERT(call, "Invalid call object"); - hfp_ag_lock(ag); - if (ag->selected_codec_id == 0) { - LOG_ERR("Codec is invalid"); - hfp_ag_unlock(ag); - return -EINVAL; - } + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_INCOMING); - if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) { - LOG_ERR("Codec is unsupported (codec id %d)", ag->selected_codec_id); - hfp_ag_unlock(ag); - return -EINVAL; + if (bt_ag && bt_ag->incoming) { + bt_ag->incoming(ag, call, call->number); } - hfp_ag_unlock(ag); - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BCS:%d\r\n", ag->selected_codec_id); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { + int err; + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err) { + bt_hfp_ag_call_reject(ag, user_data); + } + } else { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, false); } - return err; } -static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag) +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static void bt_hfp_ag_2nd_incoming_cb(struct bt_hfp_ag *ag, void *user_data) { - int err; - uint32_t hf_codec_ids; + struct bt_hfp_ag_call *call; - hfp_ag_lock(ag); - hf_codec_ids = ag->hf_codec_ids; - hfp_ag_unlock(ag); + call = (struct bt_hfp_ag_call *)user_data; - if ((hf_codec_ids != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED)) { - atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CONN); - err = bt_hfp_ag_codec_select(ag); - } else { - err = hfp_ag_open_sco(ag); + __ASSERT(call, "Invalid call object"); + + if (bt_ag && bt_ag->incoming) { + bt_ag->incoming(ag, call, call->number); } - return err; + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); } -static void bt_hfp_ag_audio_connection(struct bt_hfp_ag *ag, void *user_data) +static void bt_hfp_ag_ccwa_cb(struct bt_hfp_ag *ag, void *user_data) { int err; - err = bt_hfp_ag_create_audio_connection(ag); + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_INCOMING, + bt_hfp_ag_2nd_incoming_cb, user_data); if (err) { - bt_hfp_ag_unit_call_terminate(ag, user_data); + LOG_ERR("Fail to send call setup"); } } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ -static void bt_hfp_ag_unit_call_accept(struct bt_hfp_ag *ag, void *user_data) +int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) { - int err; - - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, user_data); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); - } - - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_audio_connection, user_data); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); - } -} + int err = 0; + size_t len; + struct bt_hfp_ag_call *call; + int call_count; -static int bt_hfp_ag_ata_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ - int err; + LOG_DBG(""); - if (!is_char(buf, '\r')) { - return -ENOTSUP; + if (ag == NULL) { + return -EINVAL; } hfp_ag_lock(ag); - if (ag->call_state != BT_HFP_CALL_ALERTING) { + if (ag->state != BT_HFP_CONNECTED) { hfp_ag_unlock(ag); - return -ENOTSUP; + return -ENOTCONN; } hfp_ag_unlock(ag); - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - return -ENOTSUP; + len = strlen(number); + if ((len == 0) || (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN)) { + return -EINVAL; } - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_accept, NULL); - - return err; -} - -static int bt_hfp_ag_cops_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ - int err; - uint32_t number; - - if (is_char(buf, '=')) { - static const uint32_t command_line_prefix[] = {3, 0}; - - for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) { - err = get_number(buf, &number); - if (err != 0) { - return -ENOTSUP; - } + call = get_call_from_number(ag, number, 0); + if (call) { + return -EBUSY; + } - if (command_line_prefix[i] != number) { - return -ENOTSUP; - } + call_count = get_none_released_calls(ag); + if (call_count) { +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_WRN("3 Way call feature is not supported on both sides"); + return -ENOEXEC; + } - if (!is_char(buf, ',')) { - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } - } + if (!atomic_test_bit(ag->flags, BT_HFP_AG_CCWA_ENABLE)) { + LOG_WRN("Call waiting notification is not enabled"); + return -ENOTSUP; } - atomic_set_bit(ag->flags, BT_HFP_AG_COPS_SET); - return 0; + if (!get_active_held_calls(ag)) { + LOG_WRN("The first call is not accepted"); + return -EBUSY; + } +#else + return -EBUSY; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ } - if (!atomic_test_bit(ag->flags, BT_HFP_AG_COPS_SET)) { - return -ENOTSUP; + call = get_new_call(ag, number, 0); + if (!call) { + LOG_WRN("Cannot allocate a new call object"); + return -ENOMEM; } - if (!is_char(buf, '?')) { - return -ENOTSUP; - } + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_INCOMING); - if (!is_char(buf, '\r')) { - return -ENOTSUP; +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (call_count) { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY); + err = hfp_ag_send_data(ag, bt_hfp_ag_ccwa_cb, call, "\r\n+CCWA:\"%s\",%d\r\n", + call->number, call->type); + if (err) { + LOG_WRN("Fail to send call waiting notification"); + free_call(call); + } + + return err; } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+COPS:%d,%d,\"%s\"\r\n", 0, 0, ag->operator); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_INCOMING, + bt_hfp_ag_incoming_cb, call); + if (err) { + free_call(call); } return err; } -static int bt_hfp_ag_bcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +int bt_hfp_ag_hold_incoming(struct bt_hfp_ag_call *call) { - int err; + int err = 0; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; - if (!is_char(buf, '\r')) { - return -ENOTSUP; - } + LOG_DBG(""); - hfp_ag_lock(ag); - if ((ag->selected_codec_id == 0) || - (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) || - (ag->call_state == BT_HFP_CALL_TERMINATE) || - (ag->sco_chan.sco != NULL)) { - hfp_ag_unlock(ag); - return -ENOTSUP; + if (call == NULL) { + return -EINVAL; } - hfp_ag_unlock(ag); - - err = hfp_ag_next_step(ag, bt_hfp_ag_audio_connection, NULL); - - return 0; -} -static void bt_hfp_ag_unit_codec_conn_setup(struct bt_hfp_ag *ag, void *user_data) -{ - int err = hfp_ag_open_sco(ag); + ag = call->ag; - if (err) { - bt_hfp_ag_call_reject(ag, user_data); + if (ag == NULL) { + return -EINVAL; } -} - -static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ - int err; - uint32_t number; - if (!is_char(buf, '=')) { - return -ENOTSUP; + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; } - err = get_number(buf, &number); - if (err != 0) { - return -ENOTSUP; - } + call_state = call->call_state; + hfp_ag_unlock(ag); - if (!is_char(buf, '\r')) { - return -ENOTSUP; + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + return -EINVAL; } - if (!atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { - return -ESRCH; + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_held_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; } - hfp_ag_lock(ag); - if (ag->selected_codec_id != number) { - LOG_ERR("Received codec id %d is not aligned with selected %d", - number, ag->selected_codec_id); - err = -ENOTSUP; - } else if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) { - LOG_ERR("Selected codec id %d is unsupported %d", - ag->selected_codec_id, ag->hf_codec_ids); - err = -ENOTSUP; - } - hfp_ag_unlock(ag); + return -EINVAL; +} - atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN); - atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); +int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) +{ + int err = 0; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; - if (err == 0) { - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_codec_conn_setup, NULL); - } else { - bt_hfp_call_state_t call_state; + LOG_DBG(""); - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); - if (call_state != BT_HFP_CALL_TERMINATE) { - (void)hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, NULL); - } + if (call == NULL) { + return -EINVAL; } - return err; -} + ag = call->ag; -static void bt_hfp_ag_unit_call_outgoing(struct bt_hfp_ag *ag, void *user_data) -{ - int err = bt_hfp_ag_outgoing(ag, ag->number); + if (ag == NULL) { + return -EINVAL; + } - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; } -} -static int bt_hfp_ag_atd_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ - int err; - char *number = NULL; - bool is_memory_dial = false; - size_t len; + call_state = call->call_state; + hfp_ag_unlock(ag); - if (buf->data[buf->len - 1] != '\r') { - return -ENOTSUP; + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + return -EINVAL; } - if (is_char(buf, '>')) { - is_memory_dial = true; + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_REJECT_CALL)) { + LOG_ERR("AG has not ability to reject call"); + return -ENOTSUP; } - if ((buf->len - 1) > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) { - return -ENAMETOOLONG; - } + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { + uint8_t call_setup; - buf->data[buf->len - 1] = '\0'; + hfp_ag_lock(ag); + call_setup = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + hfp_ag_unlock(ag); - if (is_memory_dial) { - if (bt_ag && bt_ag->memory_dial) { - err = bt_ag->memory_dial(ag, &buf->data[0], &number); - if ((err != 0) || (number == NULL)) { - return -ENOTSUP; + if (call_setup) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, NULL, NULL); + if (err) { + return err; + } } - } else { - return -ENOTSUP; + + ag_reject_call(call); + + return 0; } - } else { - number = &buf->data[0]; } - len = strlen(number); - if (len == 0) { - return -ENOTSUP; - } + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, call); - if (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) { - return -ENAMETOOLONG; + return err; } - hfp_ag_lock(ag); - if (ag->call_state != BT_HFP_CALL_TERMINATE) { - hfp_ag_unlock(ag); - return -EBUSY; + if ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { + err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; } - /* Copy number to ag->number including null-character */ - memcpy(ag->number, number, len + 1); - hfp_ag_unlock(ag); - - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, NULL); - - return err; + return -EINVAL; } -static int bt_hfp_ag_bldn_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +int bt_hfp_ag_accept(struct bt_hfp_ag_call *call) { - int err; + int err = 0; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; - if (!is_char(buf, '\r')) { - return -ENOTSUP; + LOG_DBG(""); + + if (call == NULL) { + return -EINVAL; } - hfp_ag_lock(ag); - if (strlen(ag->number) == 0) { - hfp_ag_unlock(ag); - return -ENOSR; + ag = call->ag; + + if (ag == NULL) { + return -EINVAL; } - if (ag->call_state != BT_HFP_CALL_TERMINATE) { + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { hfp_ag_unlock(ag); - return -EBUSY; + return -ENOTCONN; } + + call_state = call->call_state; hfp_ag_unlock(ag); - err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, NULL); + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + return -EINVAL; + } - return err; -} +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { + if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { + return chld_deactivate_calls_and_accept_other_call(ag, false); + } + } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ -static int bt_hfp_ag_clip_handler(struct bt_hfp_ag *ag, struct net_buf *buf) -{ - int err; - uint32_t clip; + if (call_state == BT_HFP_CALL_ALERTING) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, call); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } - err = get_number(buf, &clip); - if (err != 0) { - return -ENOTSUP; - } + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_call_setup_none_cb, call); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } - if (!is_char(buf, '\r')) { - return -ENOTSUP; + return err; } - if (clip > 1) { - return -ENOTSUP; + if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, btrh_accept_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ACCEPTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; } - atomic_set_bit_to(ag->flags, BT_HFP_AG_CLIP_ENABLE, clip == 1); - - return err; + return -EINVAL; } -static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { - {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, - {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, - {"AT+CHLD", bt_hfp_ag_chld_handler}, {"AT+BIND", bt_hfp_ag_bind_handler}, - {"AT+CMEE", bt_hfp_ag_cmee_handler}, {"AT+CHUP", bt_hfp_ag_chup_handler}, - {"AT+CLCC", bt_hfp_ag_clcc_handler}, {"AT+BIA", bt_hfp_ag_bia_handler}, - {"ATA", bt_hfp_ag_ata_handler}, {"AT+COPS", bt_hfp_ag_cops_handler}, - {"AT+BCC", bt_hfp_ag_bcc_handler}, {"AT+BCS", bt_hfp_ag_bcs_handler}, - {"ATD", bt_hfp_ag_atd_handler}, {"AT+BLDN", bt_hfp_ag_bldn_handler}, - {"AT+CLIP", bt_hfp_ag_clip_handler}, -}; - -static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) +int bt_hfp_ag_terminate(struct bt_hfp_ag_call *call) { - struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + int err = 0; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; - bt_hfp_ag_set_state(ag, BT_HFP_CONFIG); + LOG_DBG(""); - LOG_DBG("AG %p", ag); -} + if (call == NULL) { + return -EINVAL; + } -static void hfp_ag_disconnected(struct bt_rfcomm_dlc *dlc) -{ - struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); - bt_hfp_call_state_t call_state; - sys_snode_t *node; - struct bt_ag_tx *tx; + ag = call->ag; - k_work_cancel_delayable(&ag->tx_work); + if (ag == NULL) { + return -EINVAL; + } hfp_ag_lock(ag); - node = sys_slist_get(&ag->tx_pending); - hfp_ag_unlock(ag); - tx = CONTAINER_OF(node, struct bt_ag_tx, node); - while (tx) { - if (tx->buf && !atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { - net_buf_unref(tx->buf); - } - tx->err = -ESHUTDOWN; - k_fifo_put(&ag_tx_notify, tx); - hfp_ag_lock(ag); - node = sys_slist_get(&ag->tx_pending); + if (ag->state != BT_HFP_CONNECTED) { hfp_ag_unlock(ag); - tx = CONTAINER_OF(node, struct bt_ag_tx, node); + return -ENOTCONN; } - bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTED); - - hfp_ag_lock(ag); - call_state = ag->call_state; + call_state = call->call_state; hfp_ag_unlock(ag); - if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_ACTIVE) || - (call_state == BT_HFP_CALL_HOLD)) { - bt_hfp_ag_terminate_cb(ag, NULL); + + if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) && + (call_state == BT_HFP_CALL_ACTIVE)) { + err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + return err; } - LOG_DBG("AG %p", ag); + if ((call_state != BT_HFP_CALL_ACTIVE) && (call_state != BT_HFP_CALL_HOLD)) { + return -EINVAL; + } + + ag_terminate_call(call); + + return 0; } -static void hfp_ag_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) +int bt_hfp_ag_retrieve(struct bt_hfp_ag_call *call) { - struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); - uint8_t *data = buf->data; - uint16_t len = buf->len; - enum at_cme cme_err; - int err = -EOPNOTSUPP; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; - LOG_HEXDUMP_DBG(data, len, "Received:"); + LOG_DBG(""); - for (uint32_t index = 0; index < ARRAY_SIZE(cmd_handlers); index++) { - if (strlen(cmd_handlers[index].cmd) > len) { - continue; - } - if (strncmp((char *)data, cmd_handlers[index].cmd, - strlen(cmd_handlers[index].cmd)) != 0) { - continue; - } - if (NULL != cmd_handlers[index].handler) { - (void)net_buf_pull(buf, strlen(cmd_handlers[index].cmd)); - err = cmd_handlers[index].handler(ag, buf); - LOG_DBG("AT commander is handled (err %d)", err); - break; - } + if (call == NULL) { + return -EINVAL; } - if ((err != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CMEE_ENABLE)) { - cme_err = bt_hfp_ag_get_cme_err(err); - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CME ERROR:%d\r\n", (uint32_t)cme_err); - } else { - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n%s\r\n", (err == 0) ? "OK" : "ERROR"); + ag = call->ag; + + if (ag == NULL) { + return -EINVAL; } - if (err != 0) { - LOG_ERR("HFP AG send response err :(%d)", err); + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; } -} -static void bt_hfp_ag_thread(void *p1, void *p2, void *p3) -{ - struct bt_ag_tx *tx; - bt_hfp_ag_tx_cb_t cb; - struct bt_hfp_ag *ag; - void *user_data; - bt_hfp_state_t state; - int err; + call_state = call->call_state; + hfp_ag_unlock(ag); - while (true) { - tx = (struct bt_ag_tx *)k_fifo_get(&ag_tx_notify, K_FOREVER); + if (call_state != BT_HFP_CALL_HOLD) { + return -EINVAL; + } - if (tx == NULL) { - continue; - } + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); - cb = tx->cb; - ag = tx->ag; - user_data = tx->user_data; - err = tx->err; + if (bt_ag && bt_ag->retrieve) { + bt_ag->retrieve(call); + } - bt_ag_tx_free(tx); + ag_notify_call_held_ind(ag, NULL); - if (err < 0) { - hfp_ag_lock(ag); - state = ag->state; - hfp_ag_unlock(ag); - if ((state != BT_HFP_DISCONNECTED) && (state != BT_HFP_DISCONNECTING)) { - bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTING); - bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); - } - } + return 0; +} + +int bt_hfp_ag_hold(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; + + LOG_DBG(""); - if (cb) { - cb(ag, user_data); - } + if (call == NULL) { + return -EINVAL; } -} -static void hfp_ag_sent(struct bt_rfcomm_dlc *dlc, int err) -{ - struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); - sys_snode_t *node; - struct bt_ag_tx *tx; + ag = call->ag; + + if (ag == NULL) { + return -EINVAL; + } hfp_ag_lock(ag); - /* Clear the tx ongoing flag */ - if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { - LOG_WRN("tx ongoing flag is not set"); + if (ag->state != BT_HFP_CONNECTED) { hfp_ag_unlock(ag); - return; + return -ENOTCONN; } - node = sys_slist_get(&ag->tx_pending); + call_state = call->call_state; hfp_ag_unlock(ag); - if (!node) { - LOG_ERR("No pending tx"); - return; + + if (call_state != BT_HFP_CALL_ACTIVE) { + return -EINVAL; } - tx = CONTAINER_OF(node, struct bt_ag_tx, node); - LOG_ERR("Completed pending tx %p", tx); + if ((call_state == BT_HFP_CALL_ACTIVE) && + (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { + return -EINVAL; + } - /* Restart the tx work */ - k_work_reschedule(&ag->tx_work, K_NO_WAIT); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); - tx->err = err; - k_fifo_put(&ag_tx_notify, tx); + if (bt_ag && bt_ag->held) { + bt_ag->held(call); + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; } -static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data) +int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number) { - int err; - bt_hfp_call_state_t call_state; - LOG_DBG(""); + if (ag == NULL) { + return -EINVAL; + } + hfp_ag_lock(ag); - call_state = ag->call_state; + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } hfp_ag_unlock(ag); - switch (call_state) { - case BT_HFP_CALL_TERMINATE: - break; - case BT_HFP_CALL_ACTIVE: - break; - case BT_HFP_CALL_HOLD: - break; - case BT_HFP_CALL_OUTGOING: - __fallthrough; - case BT_HFP_CALL_INCOMING: - __fallthrough; - case BT_HFP_CALL_ALERTING: - __fallthrough; - default: - if (ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] && - ag->indicator_value[BT_HFP_AG_CALL_IND]) { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, - BT_HFP_CALL_SETUP_NONE, NULL, NULL); - if (err) { - LOG_ERR("Fail to send indicator"); - bt_hfp_ag_terminate_cb(ag, NULL); - } else { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, - bt_hfp_ag_terminate_cb, NULL); - if (err) { - LOG_ERR("Fail to send indicator"); - bt_hfp_ag_terminate_cb(ag, NULL); - } - } - } else if (ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]) { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, - BT_HFP_CALL_SETUP_NONE, bt_hfp_ag_reject_cb, - NULL); - if (err) { - LOG_ERR("Fail to send indicator"); - bt_hfp_ag_terminate_cb(ag, NULL); - } - } else if (ag->indicator_value[BT_HFP_AG_CALL_IND]) { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, - bt_hfp_ag_terminate_cb, NULL); - if (err) { - LOG_ERR("Fail to send indicator"); - bt_hfp_ag_terminate_cb(ag, NULL); - } - } - break; - } + return bt_hfp_ag_outgoing_call(ag, number, 0); } -static void bt_ag_deferred_work(struct k_work *work) +static void bt_hfp_ag_ringing_cb(struct bt_hfp_ag *ag, void *user_data) { - struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, deferred_work); + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; - (void)hfp_ag_next_step(ag, bt_ag_deferred_work_cb, NULL); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + + if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { + bt_hfp_ag_call_ringing_cb(call, true); + } else { + bt_hfp_ag_call_ringing_cb(call, false); + } } -static void bt_ag_ringing_work_cb(struct bt_hfp_ag *ag, void *user_data) +int bt_hfp_ag_remote_ringing(struct bt_hfp_ag_call *call) { - int err; - bt_hfp_call_state_t call_state; + int err = 0; + struct bt_hfp_ag *ag; LOG_DBG(""); - hfp_ag_lock(ag); - call_state = ag->call_state; - hfp_ag_unlock(ag); - if (call_state == BT_HFP_CALL_ALERTING) { + if (call == NULL) { + return -EINVAL; + } - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - return; - } + ag = call->ag; - k_work_reschedule(&ag->ringing_work, - K_SECONDS(CONFIG_BT_HFP_AG_RING_NOTIFY_INTERVAL)); + if (ag == NULL) { + return -EINVAL; + } - err = hfp_ag_send_data(ag, NULL, NULL, "\r\nRING\r\n"); - if (err) { - LOG_ERR("Fail to send RING %d", err); - } else { - if (atomic_test_bit(ag->flags, BT_HFP_AG_CLIP_ENABLE)) { - err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLIP:\"%s\",%d\r\n", - ag->number, 0); - if (err) { - LOG_ERR("Fail to send CLIP %d", err); - } - } + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + if (call->call_state != BT_HFP_CALL_OUTGOING) { + hfp_ag_unlock(ag); + return -EBUSY; + } + + if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { + if (ag->sco_chan.sco == NULL) { + hfp_ag_unlock(ag); + return -ENOTCONN; } } -} + hfp_ag_unlock(ag); -static void bt_ag_ringing_work(struct k_work *work) -{ - struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, ringing_work); + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_REMOTE_ALERTING, bt_hfp_ag_ringing_cb, + call); - (void)hfp_ag_next_step(ag, bt_ag_ringing_work_cb, NULL); + return err; } -int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t channel) +int bt_hfp_ag_remote_reject(struct bt_hfp_ag_call *call) { - int i; int err; - - static struct bt_rfcomm_dlc_ops ops = { - .connected = hfp_ag_connected, - .disconnected = hfp_ag_disconnected, - .recv = hfp_ag_recv, - .sent = hfp_ag_sent, - }; + struct bt_hfp_ag *ag; LOG_DBG(""); - if (ag == NULL) { + if (call == NULL) { return -EINVAL; } - *ag = NULL; + ag = call->ag; - if (ag_thread_id == NULL) { + if (ag == NULL) { + return -EINVAL; + } - k_fifo_init(&ag_tx_free); - k_fifo_init(&ag_tx_notify); + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } - for (i = 0; i < ARRAY_SIZE(ag_tx); i++) { - k_fifo_put(&ag_tx_free, &ag_tx[i]); - } + if ((call->call_state != BT_HFP_CALL_ALERTING) && + (call->call_state != BT_HFP_CALL_OUTGOING)) { + hfp_ag_unlock(ag); + return -EINVAL; + } + hfp_ag_unlock(ag); - ag_thread_id = k_thread_create( - &ag_thread, ag_thread_stack, K_KERNEL_STACK_SIZEOF(ag_thread_stack), - bt_hfp_ag_thread, NULL, NULL, NULL, - K_PRIO_COOP(CONFIG_BT_HFP_AG_THREAD_PRIO), 0, K_NO_WAIT); - if (ag_thread_id == NULL) { - return -ENOMEM; - } - k_thread_name_set(ag_thread_id, "HFP AG"); + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + return -EINVAL; } - for (i = 0; i < ARRAY_SIZE(bt_hfp_ag_pool); i++) { - struct bt_hfp_ag *_ag = &bt_hfp_ag_pool[i]; + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, call); - if (_ag->rfcomm_dlc.session) { - continue; - } + return err; +} + +int bt_hfp_ag_remote_accept(struct bt_hfp_ag_call *call) +{ + int err; + struct bt_hfp_ag *ag; - (void)memset(_ag, 0, sizeof(struct bt_hfp_ag)); + LOG_DBG(""); - sys_slist_init(&_ag->tx_pending); + if (call == NULL) { + return -EINVAL; + } - k_sem_init(&_ag->lock, 1, 1); + ag = call->ag; - _ag->rfcomm_dlc.ops = &ops; - _ag->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; + if (ag == NULL) { + return -EINVAL; + } - /* Set the supported features*/ - _ag->ag_features = BT_HFP_AG_SUPPORTED_FEATURES; + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } - /* If supported codec ids cannot be notified, disable codec negotiation. */ - if (!(bt_ag && bt_ag->codec)) { - _ag->ag_features &= ~BT_HFP_AG_FEATURE_CODEC_NEG; - } + if (call->call_state != BT_HFP_CALL_ALERTING) { + hfp_ag_unlock(ag); + return -EINVAL; + } + hfp_ag_unlock(ag); - _ag->hf_features = 0; - _ag->hf_codec_ids = 0; + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { + return -EINVAL; + } - _ag->acl_conn = conn; + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, call); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } + } - /* Set AG indicator value */ - _ag->indicator_value[BT_HFP_AG_SERVICE_IND] = 0; - _ag->indicator_value[BT_HFP_AG_CALL_IND] = 0; - _ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = 0; - _ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = 0; - _ag->indicator_value[BT_HFP_AG_SIGNAL_IND] = 0; - _ag->indicator_value[BT_HFP_AG_ROAM_IND] = 0; - _ag->indicator_value[BT_HFP_AG_BATTERY_IND] = 0; + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_call_setup_none_cb, call); + if (err) { + LOG_ERR("Fail to send err :(%d)", err); + } - /* Set AG indicator status */ - _ag->indicator = BIT(BT_HFP_AG_SERVICE_IND) | BIT(BT_HFP_AG_CALL_IND) | - BIT(BT_HFP_AG_CALL_SETUP_IND) | BIT(BT_HFP_AG_CALL_HELD_IND) | - BIT(BT_HFP_AG_SIGNAL_IND) | BIT(BT_HFP_AG_ROAM_IND) | - BIT(BT_HFP_AG_BATTERY_IND); + return err; +} - /* Set AG operator */ - memcpy(_ag->operator, "UNKNOWN", sizeof("UNKNOWN")); +int bt_hfp_ag_remote_terminate(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; - /* Set Codec ID*/ - _ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; + LOG_DBG(""); - /* Init delay work */ - k_work_init_delayable(&_ag->deferred_work, bt_ag_deferred_work); + if (call == NULL) { + return -EINVAL; + } - k_work_init_delayable(&_ag->ringing_work, bt_ag_ringing_work); + ag = call->ag; - k_work_init_delayable(&_ag->tx_work, bt_ag_tx_work); + if (ag == NULL) { + return -EINVAL; + } - atomic_set_bit(_ag->flags, BT_HFP_AG_CODEC_CHANGED); + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } - *ag = _ag; + if ((call->call_state != BT_HFP_CALL_ACTIVE) && (call->call_state != BT_HFP_CALL_HOLD)) { + hfp_ag_unlock(ag); + return -EINVAL; } + hfp_ag_unlock(ag); - if (*ag == NULL) { - return -ENOMEM; + ag_terminate_call(call); + + return 0; +} + +int bt_hfp_ag_explicit_call_transfer(struct bt_hfp_ag *ag) +{ +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (ag == NULL) { + return -EINVAL; } - err = bt_rfcomm_dlc_connect(conn, &(*ag)->rfcomm_dlc, channel); - if (err != 0) { - (void)memset(*ag, 0, sizeof(struct bt_hfp_ag)); - *ag = NULL; - } else { - bt_hfp_ag_set_state(*ag, BT_HFP_CONNECTING); + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; } + hfp_ag_unlock(ag); - return err; + return chld_drop_conversation(ag); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ } -int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag) +int bt_hfp_ag_set_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, uint8_t value) { int err; - bt_hfp_call_state_t call_state; LOG_DBG(""); @@ -1844,70 +4427,62 @@ int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag) } hfp_ag_lock(ag); - call_state = ag->call_state; + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; + } hfp_ag_unlock(ag); - bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTING); - - if ((call_state == BT_HFP_CALL_ACTIVE) || (call_state == BT_HFP_CALL_HOLD)) { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, - NULL); - if (err != 0) { - LOG_ERR("HFP AG send response err :(%d)", err); - } - return err; - } else if (call_state != BT_HFP_CALL_TERMINATE) { - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_reject_cb, NULL); - if (err != 0) { - LOG_ERR("HFP AG send response err :(%d)", err); + switch (index) { + case BT_HFP_AG_SERVICE_IND: + case BT_HFP_AG_SIGNAL_IND: + case BT_HFP_AG_ROAM_IND: + case BT_HFP_AG_BATTERY_IND: + if ((ag_ind[(uint8_t)index].min > value) || (ag_ind[(uint8_t)index].max < value)) { + return -EINVAL; } - return err; + break; + case BT_HFP_AG_CALL_IND: + case BT_HFP_AG_CALL_SETUP_IND: + case BT_HFP_AG_CALL_HELD_IND: + default: + return -EINVAL; } - return bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); + err = hfp_ag_update_indicator(ag, index, value, NULL, NULL); + + return err; } -int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb) +int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name) { - if (!cb) { + int len; + + LOG_DBG(""); + + if (ag == NULL) { return -EINVAL; } - if (bt_ag) { - return -EALREADY; + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) { + hfp_ag_unlock(ag); + return -ENOTCONN; } - bt_ag = cb; + len = strlen(name); + len = MIN(sizeof(ag->operator) - 1, len); + memcpy(ag->operator, name, len); + ag->operator[len] = '\0'; + ag->mode = mode; + hfp_ag_unlock(ag); return 0; } -static void bt_hfp_ag_incoming_cb(struct bt_hfp_ag *ag, void *user_data) -{ - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_INCOMING); - - if (bt_ag && bt_ag->incoming) { - bt_ag->incoming(ag, ag->number); - } - - if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { - int err; - - err = bt_hfp_ag_create_audio_connection(ag); - if (err) { - bt_hfp_ag_call_reject(ag, NULL); - } - } else { - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ALERTING); - bt_hfp_ag_call_ringing_cb(ag, false); - } -} - -int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) +int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id) { - int err = 0; - size_t len; + int err; LOG_DBG(""); @@ -1921,34 +4496,42 @@ int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) return -ENOTCONN; } - if (ag->call_state != BT_HFP_CALL_TERMINATE) { + if (ag->hf_codec_ids) { + if (!(ag->hf_codec_ids & BIT(id))) { + hfp_ag_unlock(ag); + return -EINVAL; + } + } else { + if (id != BT_HFP_AG_CODEC_CVSD) { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + } + + if (ag->sco_chan.sco) { + LOG_ERR("Audio conenction has been connected"); hfp_ag_unlock(ag); - return -EBUSY; + return -ECONNREFUSED; } hfp_ag_unlock(ag); - len = strlen(number); - if ((len == 0) || (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN)) { - return -EINVAL; + if (atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { + return -EBUSY; } hfp_ag_lock(ag); - /* Copy number to ag->number including null-character */ - memcpy(ag->number, number, len + 1); + if (ag->selected_codec_id != id) { + atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + } + ag->selected_codec_id = id; hfp_ag_unlock(ag); - atomic_set_bit(ag->flags, BT_HFP_AG_INCOMING_CALL); - - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_INCOMING, - bt_hfp_ag_incoming_cb, NULL); - if (err != 0) { - atomic_clear_bit(ag->flags, BT_HFP_AG_INCOMING_CALL); - } + err = bt_hfp_ag_create_audio_connection(ag, NULL); return err; } -int bt_hfp_ag_reject(struct bt_hfp_ag *ag) +int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm) { int err; @@ -1958,29 +4541,32 @@ int bt_hfp_ag_reject(struct bt_hfp_ag *ag) return -EINVAL; } + if (vgm > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid VGM (%d>%d)", vgm, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + hfp_ag_lock(ag); if (ag->state != BT_HFP_CONNECTED) { hfp_ag_unlock(ag); return -ENOTCONN; } - - if ((ag->call_state != BT_HFP_CALL_ALERTING) && (ag->call_state != BT_HFP_CALL_INCOMING)) { - hfp_ag_unlock(ag); - return -EINVAL; - } hfp_ag_unlock(ag); - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - return -EINVAL; + if (!HF_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOLUME)) { + LOG_ERR("Remote Audio Volume Control is unsupported"); + return -ENOTSUP; } - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_reject_cb, NULL); + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGM=%d\r\n", vgm); + if (err) { + LOG_ERR("Fail to notify vgm err :(%d)", err); + } return err; } -int bt_hfp_ag_accept(struct bt_hfp_ag *ag) +int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs) { int err; @@ -1990,37 +4576,32 @@ int bt_hfp_ag_accept(struct bt_hfp_ag *ag) return -EINVAL; } + if (vgs > BT_HFP_HF_VGS_GAIN_MAX) { + LOG_ERR("Invalid VGM (%d>%d)", vgs, BT_HFP_HF_VGS_GAIN_MAX); + return -EINVAL; + } + hfp_ag_lock(ag); if (ag->state != BT_HFP_CONNECTED) { hfp_ag_unlock(ag); return -ENOTCONN; } - - if (ag->call_state != BT_HFP_CALL_ALERTING) { - hfp_ag_unlock(ag); - return -EINVAL; - } hfp_ag_unlock(ag); - if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - return -EINVAL; - } - - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, NULL); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + if (!HF_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOLUME)) { + LOG_ERR("Remote Audio Volume Control is unsupported"); + return -ENOTSUP; } - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_audio_connection, NULL); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGS=%d\r\n", vgs); + if (err) { + LOG_ERR("Fail to notify vgs err :(%d)", err); } return err; } -int bt_hfp_ag_terminate(struct bt_hfp_ag *ag) +int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband) { int err; @@ -2035,40 +4616,24 @@ int bt_hfp_ag_terminate(struct bt_hfp_ag *ag) hfp_ag_unlock(ag); return -ENOTCONN; } - - if ((ag->call_state != BT_HFP_CALL_ACTIVE) && (ag->call_state != BT_HFP_CALL_HOLD)) { - hfp_ag_unlock(ag); - return -EINVAL; - } hfp_ag_unlock(ag); - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, NULL); - - return err; -} - -static void bt_hfp_ag_outgoing_cb(struct bt_hfp_ag *ag, void *user_data) -{ - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_OUTGOING); - - if (bt_ag && bt_ag->outgoing) { - bt_ag->outgoing(ag, ag->number); + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR=%d\r\n", inband ? 1 : 0); + if (err) { + LOG_ERR("Fail to set inband ringtone err :(%d)", err); + return err; } - if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { - int err; - - err = bt_hfp_ag_create_audio_connection(ag); - if (err) { - bt_hfp_ag_call_reject(ag, NULL); - } - } + atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, inband); + return 0; } -int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number) +int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate) { - int err = 0; - size_t len; +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + int err; + bool feature; + char *bvra; LOG_DBG(""); @@ -2081,45 +4646,43 @@ int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number) hfp_ag_unlock(ag); return -ENOTCONN; } - - if (ag->call_state != BT_HFP_CALL_TERMINATE) { - hfp_ag_unlock(ag); - return -EBUSY; - } hfp_ag_unlock(ag); - len = strlen(number); - if ((len == 0) || (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN)) { - return -EINVAL; + if (activate && atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("VR has been activated"); + return -ENOTSUP; + } else if (!activate && !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("VR is not activated"); + return -ENOTSUP; } - hfp_ag_lock(ag); - /* Copy number to ag->number including null-character */ - memcpy(ag->number, number, len + 1); - hfp_ag_unlock(ag); - - atomic_clear_bit(ag->flags, BT_HFP_AG_INCOMING_CALL); - - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_OUTGOING, - bt_hfp_ag_outgoing_cb, NULL); - - return err; -} - -static void bt_hfp_ag_ringing_cb(struct bt_hfp_ag *ag, void *user_data) -{ - bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ALERTING); - - if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { - bt_hfp_ag_call_ringing_cb(ag, true); + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); + if (!feature) { + bvra = ""; } else { - bt_hfp_ag_call_ringing_cb(ag, false); + bvra = ",0"; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:%d%s\r\n", activate, bvra); + if (err) { + LOG_ERR("Fail to notify VR activation :(%d)", err); + return err; } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_VRE_ACTIVATE, activate); + atomic_clear_bit(ag->flags, BT_HFP_AG_VRE_R2A); + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ } -int bt_hfp_ag_remote_ringing(struct bt_hfp_ag *ag) +int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state) { - int err = 0; +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + int err; + bool feature; LOG_DBG(""); @@ -2132,30 +4695,43 @@ int bt_hfp_ag_remote_ringing(struct bt_hfp_ag *ag) hfp_ag_unlock(ag); return -ENOTCONN; } + hfp_ag_unlock(ag); - if (ag->call_state != BT_HFP_CALL_OUTGOING) { - hfp_ag_unlock(ag); - return -EBUSY; + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("Voice Recognition is not activated"); + return -EINVAL; } - if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { - if (ag->sco_chan.sco == NULL) { - hfp_ag_unlock(ag); - return -ENOTCONN; - } + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); + if (!feature) { + return -ENOTSUP; } - hfp_ag_unlock(ag); - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, - BT_HFP_CALL_SETUP_REMOTE_ALERTING, bt_hfp_ag_ringing_cb, - NULL); + if ((state & BT_HFP_BVRA_STATE_SEND_AUDIO) && + !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { + LOG_ERR("HFP HF is not ready to accept audio input"); + return -EINVAL; + } - return err; + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:1,%d\r\n", state); + if (err) { + LOG_ERR("Fail to send state of VRE :(%d)", err); + return err; + } + + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ } -int bt_hfp_ag_remote_reject(struct bt_hfp_ag *ag) +int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, const char *id, + uint8_t type, uint8_t operation, const char *text) { +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) int err; + bool feature; LOG_DBG(""); @@ -2168,24 +4744,45 @@ int bt_hfp_ag_remote_reject(struct bt_hfp_ag *ag) hfp_ag_unlock(ag); return -ENOTCONN; } + hfp_ag_unlock(ag); - if ((ag->call_state != BT_HFP_CALL_ALERTING) && (ag->call_state != BT_HFP_CALL_OUTGOING)) { - hfp_ag_unlock(ag); + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { + LOG_WRN("Voice Recognition is not activated"); return -EINVAL; } - hfp_ag_unlock(ag); - if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); + if (!feature) { + return -ENOTSUP; + } + + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOICE_RECG_TEXT, + BT_HFP_AG_FEATURE_VOICE_RECG_TEXT); + if (!feature) { + return -ENOTSUP; + } + + if ((state & BT_HFP_BVRA_STATE_SEND_AUDIO) && + !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { + LOG_ERR("HFP HF is not ready to accept audio input"); return -EINVAL; } - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_reject_cb, NULL); + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:1,%d,%s,%d,%d,\"%s\"\r\n", + state, id, type, operation, text); + if (err) { + LOG_ERR("Fail to send state of VRE :(%d)", err); + return err; + } - return err; + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ } -int bt_hfp_ag_remote_accept(struct bt_hfp_ag *ag) +int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength) { int err; @@ -2200,32 +4797,18 @@ int bt_hfp_ag_remote_accept(struct bt_hfp_ag *ag) hfp_ag_unlock(ag); return -ENOTCONN; } - - if (ag->call_state != BT_HFP_CALL_ALERTING) { - hfp_ag_unlock(ag); - return -EINVAL; - } hfp_ag_unlock(ag); - if (atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) { - return -EINVAL; - } - - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, NULL); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); - } - - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, - bt_hfp_ag_audio_connection, NULL); - if (err != 0) { - LOG_ERR("Fail to send err :(%d)", err); + err = hfp_ag_update_indicator(ag, BT_HFP_AG_SIGNAL_IND, strength, + NULL, NULL); + if (err) { + LOG_ERR("Fail to set signal strength err :(%d)", err); } return err; } -int bt_hfp_ag_remote_terminate(struct bt_hfp_ag *ag) +int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status) { int err; @@ -2240,19 +4823,18 @@ int bt_hfp_ag_remote_terminate(struct bt_hfp_ag *ag) hfp_ag_unlock(ag); return -ENOTCONN; } - - if ((ag->call_state != BT_HFP_CALL_ACTIVE) && (ag->call_state != BT_HFP_CALL_HOLD)) { - hfp_ag_unlock(ag); - return -EINVAL; - } hfp_ag_unlock(ag); - err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, NULL); + err = hfp_ag_update_indicator(ag, BT_HFP_AG_ROAM_IND, status, + NULL, NULL); + if (err) { + LOG_ERR("Fail to set roaming status err :(%d)", err); + } return err; } -int bt_hfp_ag_set_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, uint8_t value) +int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level) { int err; @@ -2269,36 +4851,18 @@ int bt_hfp_ag_set_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index } hfp_ag_unlock(ag); - switch (index) { - case BT_HFP_AG_SERVICE_IND: - __fallthrough; - case BT_HFP_AG_SIGNAL_IND: - __fallthrough; - case BT_HFP_AG_ROAM_IND: - __fallthrough; - case BT_HFP_AG_BATTERY_IND: - if ((ag_ind[(uint8_t)index].min > value) || (ag_ind[(uint8_t)index].max < value)) { - return -EINVAL; - } - break; - case BT_HFP_AG_CALL_IND: - __fallthrough; - case BT_HFP_AG_CALL_SETUP_IND: - __fallthrough; - case BT_HFP_AG_CALL_HELD_IND: - __fallthrough; - default: - return -EINVAL; + err = hfp_ag_update_indicator(ag, BT_HFP_AG_BATTERY_IND, level, + NULL, NULL); + if (err) { + LOG_ERR("Fail to set battery level err :(%d)", err); } - err = hfp_ag_update_indicator(ag, index, value, NULL, NULL); - return err; } -int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, char *name) +int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available) { - int len; + int err; LOG_DBG(""); @@ -2311,19 +4875,22 @@ int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, char *name) hfp_ag_unlock(ag); return -ENOTCONN; } - - len = strlen(name); - len = MIN(sizeof(ag->operator) - 1, len); - memcpy(ag->operator, name, len); - ag->operator[len] = '\0'; hfp_ag_unlock(ag); - return 0; + err = hfp_ag_update_indicator(ag, BT_HFP_AG_SERVICE_IND, + available ? 1 : 0, NULL, NULL); + if (err) { + LOG_ERR("Fail to set service availability err :(%d)", err); + } + + return err; } -int bt_hfp_ag_select_codec(struct bt_hfp_ag *ag, uint8_t id) +int bt_hfp_ag_hf_indicator(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, bool enable) { +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) int err; + uint32_t supported_indicators; LOG_DBG(""); @@ -2337,23 +4904,30 @@ int bt_hfp_ag_select_codec(struct bt_hfp_ag *ag, uint8_t id) return -ENOTCONN; } - if (!(ag->hf_codec_ids && BIT(id))) { - hfp_ag_unlock(ag); - return -ENOTSUP; - } + supported_indicators = ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; hfp_ag_unlock(ag); - if (atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { - return -EBUSY; + if (!(supported_indicators & BIT(indicator))) { + LOG_ERR("Unsupported indicator %d", indicator); + return -ENOTSUP; } hfp_ag_lock(ag); - ag->selected_codec_id = id; + if (enable) { + ag->hf_indicators |= BIT(indicator); + } else { + ag->hf_indicators &= ~BIT(indicator); + } hfp_ag_unlock(ag); - atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); - - err = bt_hfp_ag_create_audio_connection(ag); + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", + indicator, enable ? 1 : 0); + if (err) { + LOG_ERR("Fail to update registration status of indicator:(%d)", err); + } return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ } diff --git a/subsys/bluetooth/host/classic/hfp_ag_internal.h b/subsys/bluetooth/host/classic/hfp_ag_internal.h index 94191140da0a9..fb17595422b7a 100644 --- a/subsys/bluetooth/host/classic/hfp_ag_internal.h +++ b/subsys/bluetooth/host/classic/hfp_ag_internal.h @@ -9,31 +9,149 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "hfp_internal.h" + +#if defined(CONFIG_BT_HFP_AG_EXT_ERR) +#define BT_HFP_AG_FEATURE_EXT_ERR_ENABLE BT_HFP_AG_FEATURE_EXT_ERR +#else +#define BT_HFP_AG_FEATURE_EXT_ERR_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_EXT_ERR */ + +#if defined(CONFIG_BT_HFP_AG_CODEC_NEG) +#define BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE BT_HFP_AG_FEATURE_CODEC_NEG +#else +#define BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_AG_ECNR) +#define BT_HFP_AG_FEATURE_ECNR_ENABLE BT_HFP_AG_FEATURE_ECNR +#define BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE BT_HFP_AG_SDP_FEATURE_ECNR +#else +#define BT_HFP_AG_FEATURE_ECNR_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_AG_ECS) +#define BT_HFP_AG_FEATURE_ECS_ENABLE BT_HFP_AG_FEATURE_ECS +#else +#define BT_HFP_AG_FEATURE_ECS_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ECS*/ + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +#define BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE BT_HFP_AG_FEATURE_3WAY_CALL +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE BT_HFP_AG_SDP_FEATURE_3WAY_CALL +#else +#define BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_AG_ECC) +#define BT_HFP_AG_FEATURE_ECC_ENABLE BT_HFP_AG_FEATURE_ECC +#else +#define BT_HFP_AG_FEATURE_ECC_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ECC */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) +#define BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE BT_HFP_AG_FEATURE_VOICE_RECG +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_RECG +#else +#define BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) +#define BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_AG_FEATURE_ENH_VOICE_RECG +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG +#else +#define BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) +#define BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_AG_FEATURE_VOICE_RECG_TEXT +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT +#else +#define BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) +#define BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE BT_HFP_AG_FEATURE_VOICE_TAG +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_TAG +#else +#define BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) +#define BT_HFP_AG_FEATURE_HF_IND_ENABLE BT_HFP_AG_FEATURE_HF_IND +#else +#define BT_HFP_AG_FEATURE_HF_IND_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +#if defined(CONFIG_BT_HFP_AG_REJECT_CALL) +#define BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE BT_HFP_AG_FEATURE_REJECT_CALL +#else +#define BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_REJECT_CALL */ + /* HFP AG Supported features */ -#define BT_HFP_AG_SUPPORTED_FEATURES (BT_HFP_AG_FEATURE_INBAND_RINGTONE | BT_HFP_AG_FEATURE_EXT_ERR) +#define BT_HFP_AG_SUPPORTED_FEATURES (\ + BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_AG_FEATURE_INBAND_RINGTONE | \ + BT_HFP_AG_FEATURE_EXT_ERR_ENABLE | \ + BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE | \ + BT_HFP_AG_FEATURE_ECNR_ENABLE | \ + BT_HFP_AG_FEATURE_ECS_ENABLE | \ + BT_HFP_AG_FEATURE_ECC_ENABLE | \ + BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE | \ + BT_HFP_AG_FEATURE_HF_IND_ENABLE | \ + BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE) + +/* HFP AG Supported features in SDP */ +#define BT_HFP_AG_SDP_SUPPORTED_FEATURES (\ + BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE | \ + BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE) /* bt_hfp_ag flags: the flags defined here represent HFP AG parameters */ enum { BT_HFP_AG_CMEE_ENABLE, /* Extended Audio Gateway Error Result Code */ BT_HFP_AG_CMER_ENABLE, /* Indicator Events Reporting */ BT_HFP_AG_CLIP_ENABLE, /* Calling Line Identification notification */ + BT_HFP_AG_CCWA_ENABLE, /* Call Waiting notification */ BT_HFP_AG_INBAND_RING, /* In-band ring */ BT_HFP_AG_COPS_SET, /* Query Operator selection */ - BT_HFP_AG_INCOMING_CALL, /* Incoming call */ + BT_HFP_AG_AUDIO_CONN, /* Audio connection */ BT_HFP_AG_CODEC_CONN, /* Codec connection is ongoing */ BT_HFP_AG_CODEC_CHANGED, /* Codec Id Changed */ BT_HFP_AG_TX_ONGOING, /* TX is ongoing */ BT_HFP_AG_CREATING_SCO, /* SCO is creating */ + BT_HFP_AG_VRE_ACTIVATE, /* VRE is activated */ + BT_HFP_AG_VRE_R2A, /* HF is ready to accept audio */ /* Total number of flags - must be at the end of the enum */ BT_HFP_AG_NUM_FLAGS, }; -/* HFP HF Indicators */ +/* bt_hfp_ag_call flags: the flags defined here represent HFP AG parameters */ enum { - HFP_HF_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ - HFP_HF_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */ - HFP_HF_IND_MAX + BT_HFP_AG_CALL_IN_USING, /* Object is in using */ + BT_HFP_AG_CALL_INCOMING, /* Incoming call */ + BT_HFP_AG_CALL_INCOMING_HELD, /* Incoming call held */ + BT_HFP_AG_CALL_OPEN_SCO, /* Open SCO */ + BT_HFP_AG_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */ + BT_HFP_AG_CALL_INCOMING_3WAY, /* Incoming 3 way call */ + + /* Total number of flags - must be at the end of the enum */ + BT_HFP_AG_CALL_NUM_FLAGS, }; typedef enum __packed { @@ -51,21 +169,38 @@ typedef enum __packed { typedef enum __packed { /** Call terminate */ - BT_HFP_CALL_TERMINATE, + BT_HFP_CALL_TERMINATE = 1, /** Call outgoing */ - BT_HFP_CALL_OUTGOING, + BT_HFP_CALL_OUTGOING = 2, /** Call incoming */ - BT_HFP_CALL_INCOMING, + BT_HFP_CALL_INCOMING = 4, /** Call alerting */ - BT_HFP_CALL_ALERTING, + BT_HFP_CALL_ALERTING = 8, /** Call active */ - BT_HFP_CALL_ACTIVE, + BT_HFP_CALL_ACTIVE = 16, /** Call hold */ - BT_HFP_CALL_HOLD, + BT_HFP_CALL_HOLD = 32, } bt_hfp_call_state_t; #define AT_COPS_OPERATOR_MAX_LEN 16 +struct bt_hfp_ag_call { + struct bt_hfp_ag *ag; + + char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; + uint8_t type; + + ATOMIC_DEFINE(flags, BT_HFP_AG_CALL_NUM_FLAGS); + + /* HFP Call state */ + bt_hfp_call_state_t call_state; + + /* Calling Line Identification notification */ + struct k_work_delayable ringing_work; + + struct k_work_delayable deferred_work; +}; + struct bt_hfp_ag { struct bt_rfcomm_dlc rfcomm_dlc; char buffer[HF_MAX_BUF_LEN]; @@ -84,14 +219,6 @@ struct bt_hfp_ag { /* HFP Connection state */ bt_hfp_state_t state; - /* HFP Call state */ - bt_hfp_call_state_t call_state; - - /* Delayed work deferred tasks: - * - call status cleanup. - */ - struct k_work_delayable deferred_work; - /* AG Indicators */ uint8_t indicator_value[BT_HFP_AG_IND_MAX]; uint32_t indicator; @@ -99,42 +226,27 @@ struct bt_hfp_ag { /* HF Indicators */ uint32_t hf_indicators_of_ag; uint32_t hf_indicators_of_hf; - uint8_t hf_indicator_value[HFP_HF_IND_MAX]; + uint32_t hf_indicators; /* operator */ + uint8_t mode; char operator[AT_COPS_OPERATOR_MAX_LEN + 1]; + /* calls */ + struct bt_hfp_ag_call calls[CONFIG_BT_HFP_AG_MAX_CALLS]; + + /* last dialing number and type */ + char last_number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; + uint8_t type; + /* SCO Connection Object */ struct bt_sco_chan sco_chan; /* HFP TX pending */ sys_slist_t tx_pending; - /* Dial number or incoming number */ - char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; - - /* Calling Line Identification notification */ - struct k_work_delayable ringing_work; - /* Critical locker */ struct k_sem lock; /* TX work */ struct k_work_delayable tx_work; }; - -/* Active */ -#define HFP_AG_CLCC_STATUS_ACTIVE 0 -/* Held */ -#define HFP_AG_CLCC_STATUS_HELD 1 -/* Dialing (outgoing calls only) */ -#define HFP_AG_CLCC_STATUS_DIALING 2 -/* Alerting (outgoing calls only) */ -#define HFP_AG_CLCC_STATUS_ALERTING 3 -/* Incoming (incoming calls only) */ -#define HFP_AG_CLCC_STATUS_INCOMING 4 -/* Waiting (incoming calls only) */ -#define HFP_AG_CLCC_STATUS_WAITING 5 -/* Call held by Response and Hold */ -#define HFP_AG_CLCC_STATUS_CALL_HELD_HOLD 6 -/* Invalid status */ -#define HFP_AG_CLCC_STATUS_INVALID 0xFFU diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 54d90b876f187..ceeabf0ead97a 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -26,7 +26,7 @@ #include "rfcomm_internal.h" #include "at.h" #include "sco_internal.h" -#include "hfp_internal.h" +#include "hfp_hf_internal.h" #define LOG_LEVEL CONFIG_BT_HFP_HF_LOG_LEVEL #include @@ -42,6 +42,29 @@ NET_BUF_POOL_FIXED_DEFINE(hf_pool, CONFIG_BT_MAX_CONN + 1, static struct bt_hfp_hf bt_hfp_hf_pool[CONFIG_BT_MAX_CONN]; +#define HF_ENHANCED_CALL_STATUS_TIMEOUT 50 /* ms */ + +struct at_callback_set { + void *resp; + void *finish; +} __packed; + +static inline void make_at_callback_set(void *storage, void *resp, void *finish) +{ + ((struct at_callback_set *)storage)->resp = resp; + ((struct at_callback_set *)storage)->finish = finish; +} + +static inline void *at_callback_set_resp(void *storage) +{ + return ((struct at_callback_set *)storage)->resp; +} + +static inline void *at_callback_set_finish(void *storage) +{ + return ((struct at_callback_set *)storage)->finish; +} + /* The order should follow the enum hfp_hf_ag_indicators */ static const struct { char *name; @@ -119,7 +142,7 @@ static struct bt_sdp_attribute hfp_attrs[] = { /* The values of the “SupportedFeatures” bitmap shall be the same as the * values of the Bits 0 to 4 of the AT-command AT+BRSF (see Section 5.3). */ - BT_SDP_SUPPORTED_FEATURES(BT_HFP_HF_SUPPORTED_FEATURES & 0x1f), + BT_SDP_SUPPORTED_FEATURES(BT_HFP_HF_SDP_SUPPORTED_FEATURES), }; static struct bt_sdp_record hfp_rec = BT_SDP_RECORD(hfp_attrs); @@ -136,6 +159,89 @@ void hf_slc_error(struct at_client *hf_at) } } +static void hfp_hf_send_failed(struct bt_hfp_hf *hf) +{ + int err; + + LOG_ERR("SLC error: disconnecting"); + + err = bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc); + if (err) { + LOG_ERR("Fail to disconnect: %d", err); + } +} + +static void hfp_hf_send_data(struct bt_hfp_hf *hf); + +static int hfp_hf_common_finish(struct at_client *at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(at, struct bt_hfp_hf, at); + int err = 0; + + if (result != AT_RESULT_OK) { + LOG_WRN("Fail to send AT command (result %d, cme err %d) on %p", + result, cme_err, hf); + } + + if (hf->backup_finish) { + err = hf->backup_finish(at, result, cme_err); + hf->backup_finish = NULL; + } + + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) { + LOG_DBG("TX is done on %p", hf); + } else { + LOG_WRN("Tx is not ongoing on %p", hf); + } + + hfp_hf_send_data(hf); + return err; +} + +static void hfp_hf_send_data(struct bt_hfp_hf *hf) +{ + struct net_buf *buf; + at_resp_cb_t resp; + at_finish_cb_t finish; + int err; + + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING)) { + return; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) { + return; + } + + buf = k_fifo_get(&hf->tx_pending, K_NO_WAIT); + if (!buf) { + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING); + return; + } + + resp = (at_resp_cb_t)at_callback_set_resp(buf->user_data); + finish = (at_finish_cb_t)at_callback_set_finish(buf->user_data); + + /* + * Backup the `finish` callback. + * Provide a default finish callback to drive the next sending. + */ + hf->backup_finish = finish; + finish = hfp_hf_common_finish; + + make_at_callback_set(buf->user_data, NULL, NULL); + at_register(&hf->at, resp, finish); + + err = bt_rfcomm_dlc_send(&hf->rfcomm_dlc, buf); + if (err < 0) { + LOG_ERR("Rfcomm send error :(%d)", err); + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING); + net_buf_unref(buf); + hfp_hf_send_failed(hf); + } +} + int hfp_hf_send_cmd(struct bt_hfp_hf *hf, at_resp_cb_t resp, at_finish_cb_t finish, const char *format, ...) { @@ -143,15 +249,14 @@ int hfp_hf_send_cmd(struct bt_hfp_hf *hf, at_resp_cb_t resp, va_list vargs; int ret; - /* register the callbacks */ - at_register(&hf->at, resp, finish); - buf = bt_rfcomm_create_pdu(&hf_pool); if (!buf) { LOG_ERR("No Buffers!"); return -ENOMEM; } + make_at_callback_set(buf->user_data, resp, finish); + va_start(vargs, format); ret = vsnprintk(buf->data, (net_buf_tailroom(buf) - 1), format, vargs); if (ret < 0) { @@ -165,11 +270,8 @@ int hfp_hf_send_cmd(struct bt_hfp_hf *hf, at_resp_cb_t resp, LOG_DBG("HF %p, DLC %p sending buf %p", hf, &hf->rfcomm_dlc, buf); - ret = bt_rfcomm_dlc_send(&hf->rfcomm_dlc, buf); - if (ret < 0) { - LOG_ERR("Rfcomm send error :(%d)", ret); - return ret; - } + k_fifo_put(&hf->tx_pending, buf); + hfp_hf_send_data(hf); return 0; } @@ -295,390 +397,3513 @@ int cind_resp(struct at_client *hf_at, struct net_buf *buf) return 0; } -void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, - uint32_t value) +static void free_call(struct bt_hfp_hf_call *call) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; - - LOG_DBG("Index :%u, Value :%u", index, value); + memset(call, 0, sizeof(*call)); +} - if (index >= ARRAY_SIZE(ag_ind)) { - LOG_ERR("Max only %zu indicators are supported", ARRAY_SIZE(ag_ind)); - return; - } +#if defined(CONFIG_BT_HFP_HF_ECS) +static struct bt_hfp_hf_call *get_call_with_index(struct bt_hfp_hf *hf, uint8_t index) +{ + struct bt_hfp_hf_call *call; - if (value > ag_ind[hf->ind_table[index]].max || - value < ag_ind[hf->ind_table[index]].min) { - LOG_ERR("Indicators out of range - value: %u", value); - return; - } + for (size_t i = 0; i < ARRAY_SIZE(hf->calls); i++) { + call = &hf->calls[i]; - switch (hf->ind_table[index]) { - case HF_SERVICE_IND: - if (bt_hf->service) { - bt_hf->service(conn, value); - } - break; - case HF_CALL_IND: - if (bt_hf->call) { - bt_hf->call(conn, value); - } - break; - case HF_CALL_SETUP_IND: - if (bt_hf->call_setup) { - bt_hf->call_setup(conn, value); - } - break; - case HF_CALL_HELD_IND: - if (bt_hf->call_held) { - bt_hf->call_held(conn, value); - } - break; - case HF_SINGNAL_IND: - if (bt_hf->signal) { - bt_hf->signal(conn, value); - } - break; - case HF_ROAM_IND: - if (bt_hf->roam) { - bt_hf->roam(conn, value); + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; } - break; - case HF_BATTERY_IND: - if (bt_hf->battery) { - bt_hf->battery(conn, value); + + if (call->index == index) { + return call; } - break; - default: - LOG_ERR("Unknown AG indicator"); - break; } + + return NULL; } -int cind_status_handle(struct at_client *hf_at) +static struct bt_hfp_hf_call *get_call_without_index(struct bt_hfp_hf *hf) { - uint32_t index = 0U; + struct bt_hfp_hf_call *call; - while (at_has_next_list(hf_at)) { - uint32_t value; - int ret; + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - ret = at_get_number(hf_at, &value); - if (ret < 0) { - LOG_ERR("could not get the value"); - return ret; + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; } - ag_indicator_handle_values(hf_at, index, value); - - index++; + if (!call->index) { + return call; + } } - return 0; + return NULL; } +#endif /* CONFIG_BT_HFP_HF_ECS */ -int cind_status_resp(struct at_client *hf_at, struct net_buf *buf) +static void hf_reject_call(struct bt_hfp_hf_call *call) { - int err; - - err = at_parse_cmd_input(hf_at, buf, "CIND", cind_status_handle, - AT_CMD_TYPE_NORMAL); - if (err < 0) { - LOG_ERR("Error parsing CMD input"); - hf_slc_error(hf_at); + if (bt_hf->reject) { + bt_hf->reject(call); } + free_call(call); +} - return 0; +static void hf_terminate_call(struct bt_hfp_hf_call *call) +{ + if (bt_hf->terminate) { + bt_hf->terminate(call); + } + free_call(call); } -int ciev_handle(struct at_client *hf_at) +static void clear_call_without_clcc(struct bt_hfp_hf *hf) { - uint32_t index, value; - int ret; + struct bt_hfp_hf_call *call; - ret = at_get_number(hf_at, &index); - if (ret < 0) { - LOG_ERR("could not get the Index"); - return ret; - } - /* The first element of the list shall have 1 */ - if (!index) { - LOG_ERR("Invalid index value '0'"); - return 0; - } + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; - ret = at_get_number(hf_at, &value); - if (ret < 0) { - LOG_ERR("could not get the value"); - return ret; - } + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } - ag_indicator_handle_values(hf_at, (index - 1), value); + if (atomic_test_and_clear_bit(call->flags, BT_HFP_HF_CALL_CLCC)) { + continue; + } - return 0; + switch (atomic_get(call->state)) { + case BT_HFP_HF_CALL_STATE_TERMINATE: + break; + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + hf_reject_call(call); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + case BT_HFP_HF_CALL_STATE_HELD: + hf_terminate_call(call); + break; + default: + free_call(call); + break; + } + } } -int ring_handle(struct at_client *hf_at) +static int clcc_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; - if (bt_hf->ring_indication) { - bt_hf->ring_indication(conn); + LOG_DBG("AT+CLCC (result %d) on %p", result, hf); + + if (result == AT_RESULT_OK) { + clear_call_without_clcc(hf); } + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING); + return 0; } -static const struct unsolicited { - const char *cmd; - enum at_cmd_type type; - int (*func)(struct at_client *hf_at); -} handlers[] = { - { "CIEV", AT_CMD_TYPE_UNSOLICITED, ciev_handle }, - { "RING", AT_CMD_TYPE_OTHER, ring_handle } -}; - -static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) +static void clear_call_clcc_state(struct bt_hfp_hf *hf) { - int i; + struct bt_hfp_hf_call *call; - for (i = 0; i < ARRAY_SIZE(handlers); i++) { - if (!strncmp(hf_at->buf, handlers[i].cmd, - strlen(handlers[i].cmd))) { - return &handlers[i]; + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; } - } - return NULL; + atomic_clear_bit(call->flags, BT_HFP_HF_CALL_CLCC); + } } -int unsolicited_cb(struct at_client *hf_at, struct net_buf *buf) +static void hf_query_current_calls(struct bt_hfp_hf *hf) { - const struct unsolicited *handler; + int err; - handler = hfp_hf_unsol_lookup(hf_at); - if (!handler) { - LOG_ERR("Unhandled unsolicited response"); - return -ENOMSG; + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return; } - if (!at_parse_cmd_input(hf_at, buf, handler->cmd, handler->func, - handler->type)) { - return 0; + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return; } - return -ENOMSG; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECS)) { + return; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECS)) { + return; + } + + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING)) { + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + return; + } + + clear_call_clcc_state(hf); + + err = hfp_hf_send_cmd(hf, NULL, clcc_finish, "AT+CLCC"); + if (err < 0) { + LOG_ERR("Fail to query current calls on %p", hf); + } } -int cmd_complete(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +static void hf_call_state_update(struct bt_hfp_hf_call *call, int state) { - struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; - struct bt_hfp_hf_cmd_complete cmd = { 0 }; + int old_state; - LOG_DBG(""); + old_state = atomic_get(call->state); + atomic_set(call->state, state); + + LOG_DBG("Call %p state update %d->%d", call, old_state, state); - switch (result) { - case AT_RESULT_OK: - cmd.type = HFP_HF_CMD_OK; + switch (state) { + case BT_HFP_HF_CALL_STATE_TERMINATE: + free_call(call); break; - case AT_RESULT_ERROR: - cmd.type = HFP_HF_CMD_ERROR; + case BT_HFP_HF_CALL_STATE_OUTGOING: break; - case AT_RESULT_CME_ERROR: - cmd.type = HFP_HF_CMD_CME_ERROR; - cmd.cme = cme_err; + case BT_HFP_HF_CALL_STATE_INCOMING: break; - default: - LOG_ERR("Unknown error code"); - cmd.type = HFP_HF_CMD_UNKNOWN_ERROR; + case BT_HFP_HF_CALL_STATE_ALERTING: + k_work_reschedule(&call->hf->deferred_work, + K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + break; + case BT_HFP_HF_CALL_STATE_WAITING: + k_work_reschedule(&call->hf->deferred_work, + K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + break; + case BT_HFP_HF_CALL_STATE_HELD: break; } - - if (bt_hf->cmd_complete_cb) { - bt_hf->cmd_complete_cb(conn, &cmd); - } - - return 0; } -int cmee_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_ECS) +static void call_state_update(struct bt_hfp_hf_call *call, uint32_t status) { - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - return -EINVAL; + switch (status) { + case BT_HFP_CLCC_STATUS_ACTIVE: + if ((atomic_get(call->state) != BT_HFP_HF_CALL_STATE_ACTIVE) || + ((atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) && + atomic_test_and_clear_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD))) { + atomic_val_t state; + + state = atomic_get(call->state); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (state == BT_HFP_HF_CALL_STATE_HELD) { + if (bt_hf->retrieve) { + bt_hf->retrieve(call); + } + } else { + if (bt_hf->accept) { + bt_hf->accept(call); + } + } + } + break; + case BT_HFP_CLCC_STATUS_HELD: + if ((atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) && + !atomic_test_and_clear_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD)) { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_HELD); + if (bt_hf->held) { + bt_hf->held(call); + } + } + break; + case BT_HFP_CLCC_STATUS_DIALING: + break; + case BT_HFP_CLCC_STATUS_ALERTING: + break; + case BT_HFP_CLCC_STATUS_INCOMING: + break; + case BT_HFP_CLCC_STATUS_WAITING: + break; + case BT_HFP_CLCC_STATUS_CALL_HELD_HOLD: + if ((atomic_get(call->state) == BT_HFP_HF_CALL_STATE_INCOMING) || + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING)) { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->incoming_held) { + bt_hf->incoming_held(call); + } + } + break; } - - return 0; } -static void slc_completed(struct at_client *hf_at) +static int clcc_handle(struct at_client *hf_at) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - struct bt_conn *conn = hf->acl; - - if (bt_hf->connected) { - bt_hf->connected(conn); + int err; + struct bt_hfp_hf_call *call; + uint32_t index; + uint32_t dir; + uint32_t status; + uint32_t mode; + uint32_t mpty; + char *number = NULL; + uint32_t type = 0; + bool incoming = false; + + err = at_get_number(hf_at, &index); + if (err < 0) { + LOG_ERR("Error getting index"); + return err; } - if (hfp_hf_send_cmd(hf, NULL, cmee_finish, "AT+CMEE=1") < 0) { - LOG_ERR("Error Sending AT+CMEE"); + call = get_call_with_index(hf, (uint8_t)index); + if (!call) { + LOG_INF("Valid call with index %d not found", index); + call = get_call_without_index(hf); + if (!call) { + LOG_INF("Not available call"); + return 0; + } + call->index = (uint8_t)index; } -} -int cmer_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) -{ - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; - } + atomic_set_bit(call->flags, BT_HFP_HF_CALL_CLCC); - slc_completed(hf_at); + err = at_get_number(hf_at, &dir); + if (err < 0) { + LOG_ERR("Error getting dir"); + return err; + } + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING) || + atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_3WAY)) { + incoming = true; + } + + if (incoming != !!dir) { + LOG_ERR("Call dir of HF is not aligned with AG"); + return 0; + } + + err = at_get_number(hf_at, &status); + if (err < 0) { + LOG_ERR("Error getting status"); + return err; + } + + err = at_get_number(hf_at, &mode); + if (err < 0) { + LOG_ERR("Error getting mode"); + return err; + } + + err = at_get_number(hf_at, &mpty); + if (err < 0) { + LOG_ERR("Error getting mpty"); + return err; + } + + number = at_get_string(hf_at); + + if (number) { + (void)at_get_number(hf_at, &type); + } + + call_state_update(call, status); + + LOG_DBG("CLCC idx %d dir %d status %d mode %d mpty %d number %s type %d", + index, dir, status, mode, mpty, number, type); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECS */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +static int bvra_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + uint32_t activate; + uint32_t state; + char *id; + char text_id[BT_HFP_BVRA_TEXT_ID_MAX_LEN + 1]; + size_t id_len; + uint32_t type; + uint32_t operation; + char *text; + + err = at_get_number(hf_at, &activate); + if (err < 0) { + LOG_ERR("Error getting activate"); + return err; + } + + if (activate) { + if (!atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + if (bt_hf->voice_recognition) { + bt_hf->voice_recognition(hf, true); + } + } + } else { + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + if (bt_hf->voice_recognition) { + bt_hf->voice_recognition(hf, false); + } + } + } + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + err = at_get_number(hf_at, &state); + if (err < 0) { + LOG_INF("Error getting VRE state"); + return 0; + } + + if (bt_hf->vre_state) { + bt_hf->vre_state(hf, (uint8_t)state); + } +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) + id = at_get_raw_string(hf_at, &id_len); + if (!id) { + LOG_INF("Error getting text ID"); + return 0; + } + + if (id_len > BT_HFP_BVRA_TEXT_ID_MAX_LEN) { + LOG_ERR("Invalid text ID length %d", id_len); + return -ENOTSUP; + } + + strncpy(text_id, id, MIN(id_len, BT_HFP_BVRA_TEXT_ID_MAX_LEN)); + text_id[MIN(id_len, BT_HFP_BVRA_TEXT_ID_MAX_LEN)] = '\0'; + + err = at_get_number(hf_at, &type); + if (err < 0) { + LOG_INF("Error getting text type"); + return 0; + } + + err = at_get_number(hf_at, &operation); + if (err < 0) { + LOG_INF("Error getting text operation"); + return 0; + } + + text = at_get_string(hf_at); + if (!text) { + LOG_INF("Error getting text string"); + return 0; + } + + if (bt_hf->textual_representation) { + bt_hf->textual_representation(hf, text_id, (uint8_t)type, + (uint8_t)operation, text); + } +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +static struct bt_hfp_hf_call *get_dialing_call(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + switch (atomic_get(call->state)) { + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_call_with_state(struct bt_hfp_hf *hf, int state) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + if (atomic_get(call->state) == state) { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_call_with_flag(struct bt_hfp_hf *hf, int flag) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + if (atomic_test_bit(call->flags, flag)) { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_call_with_state_and_flag(struct bt_hfp_hf *hf, + int state, int flag) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + if ((atomic_get(call->state) == state) && + atomic_test_bit(call->flags, flag)) { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_using_call(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + return call; + } + + return NULL; +} + +static void bt_hf_deferred_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_hf *hf = CONTAINER_OF(dwork, struct bt_hfp_hf, deferred_work); + + hf_query_current_calls(hf); +} + +static struct bt_hfp_hf_call *get_new_call(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (atomic_test_and_set_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + call->hf = hf; + + return call; + } + + return NULL; +} + +static int get_using_call_count(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + count++; + } + + return count; +} + +static void set_all_calls_held_state(struct bt_hfp_hf *hf, bool held) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + continue; + } + + if (held && (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_HELD); + if (bt_hf->held) { + bt_hf->held(call); + } + } + + if (!held && (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_HELD)) { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->retrieve) { + bt_hf->retrieve(call); + } + } + } +} + +static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value) +{ + struct bt_hfp_hf_call *call; + + LOG_DBG("call %d", value); + + if (value) { + call = get_dialing_call(hf); + if (!call) { + return; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD)) { + if (bt_hf->incoming_held) { + bt_hf->incoming_held(call); + } + } else { + if (bt_hf->accept) { + bt_hf->accept(call); + } + } + } else { + do { + call = get_using_call(hf); + if (!call) { + return; + } + + switch (atomic_get(call->state)) { + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + hf_reject_call(call); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + if (atomic_test_and_clear_bit(call->flags, + BT_HFP_HF_CALL_INCOMING_HELD)) { + hf_reject_call(call); + break; + } + __fallthrough; + case BT_HFP_HF_CALL_STATE_HELD: + hf_terminate_call(call); + break; + default: + free_call(call); + break; + } + } while (call); + } +} + +static void ag_indicator_handle_call_setup(struct bt_hfp_hf *hf, uint32_t value) +{ + struct bt_hfp_hf_call *call; + int call_count; + + call_count = get_using_call_count(hf); + + LOG_DBG("call setup %d", value); + + switch (value) { + case BT_HFP_CALL_SETUP_NONE: + if (call_count == 1) { + call = get_using_call(hf); + if (!call) { + break; + } + + if ((atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) || + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_HELD)) { + break; + } + + hf_reject_call(call); + } else { + call = get_dialing_call(hf); + if (!call) { + break; + } + + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_OUTGOING) { + LOG_INF("The outgoing is not alerted"); + hf_reject_call(call); + } else { + LOG_INF("Waiting for +CIEV: (callheld = 1)"); + k_work_reschedule(&hf->deferred_work, + K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + } + } + break; + case BT_HFP_CALL_SETUP_INCOMING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_INCOMING); + if (!call) { + call = get_new_call(hf); + if (!call) { + break; + } + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_INCOMING); + } + + if (call_count == 0) { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING); + } else { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_3WAY); + } + if (bt_hf->incoming) { + bt_hf->incoming(hf, call); + } + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_WAITING); + break; + case BT_HFP_CALL_SETUP_OUTGOING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) { + call = get_new_call(hf); + if (!call) { + break; + } + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + } + + if (call_count) { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_OUTGOING_3WAY); + } + if (bt_hf->outgoing) { + bt_hf->outgoing(hf, call); + } + break; + case BT_HFP_CALL_SETUP_REMOTE_ALERTING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) { + break; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ALERTING); + if (bt_hf->remote_ringing) { + bt_hf->remote_ringing(call); + } + break; + default: + break; + } +} + +static void ag_indicator_handle_call_held(struct bt_hfp_hf *hf, uint32_t value) +{ + struct bt_hfp_hf_call *call; + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + LOG_DBG("call setup %d", value); + + switch (value) { + case BT_HFP_CALL_HELD_NONE: + set_all_calls_held_state(hf, false); + break; + case BT_HFP_CALL_HELD_ACTIVE_HELD: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_ALERTING); + if (!call) { + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) { + break; + } + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->accept) { + bt_hf->accept(call); + } + break; + case BT_HFP_CALL_HELD_HELD: + set_all_calls_held_state(hf, true); + break; + default: + break; + } +} + +void ag_indicator_handle_values(struct at_client *hf_at, uint32_t index, + uint32_t value) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("Index :%u, Value :%u", index, value); + + if (index >= ARRAY_SIZE(ag_ind)) { + LOG_ERR("Max only %zu indicators are supported", ARRAY_SIZE(ag_ind)); + return; + } + + if (value > ag_ind[hf->ind_table[index]].max || + value < ag_ind[hf->ind_table[index]].min) { + LOG_ERR("Indicators out of range - value: %u", value); + return; + } + + switch (hf->ind_table[index]) { + case HF_SERVICE_IND: + if (bt_hf->service) { + bt_hf->service(hf, value); + } + break; + case HF_CALL_IND: + ag_indicator_handle_call(hf, value); + break; + case HF_CALL_SETUP_IND: + ag_indicator_handle_call_setup(hf, value); + break; + case HF_CALL_HELD_IND: + ag_indicator_handle_call_held(hf, value); + break; + case HF_SIGNAL_IND: + if (bt_hf->signal) { + bt_hf->signal(hf, value); + } + break; + case HF_ROAM_IND: + if (bt_hf->roam) { + bt_hf->roam(hf, value); + } + break; + case HF_BATTERY_IND: + if (bt_hf->battery) { + bt_hf->battery(hf, value); + } + break; + default: + LOG_ERR("Unknown AG indicator"); + break; + } +} + +int cind_status_handle(struct at_client *hf_at) +{ + uint32_t index = 0U; + + while (at_has_next_list(hf_at)) { + uint32_t value; + int ret; + + ret = at_get_number(hf_at, &value); + if (ret < 0) { + LOG_ERR("could not get the value"); + return ret; + } + + ag_indicator_handle_values(hf_at, index, value); + + index++; + } + + return 0; +} + +int cind_status_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + err = at_parse_cmd_input(hf_at, buf, "CIND", cind_status_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) { + LOG_ERR("Error parsing CMD input"); + hf_slc_error(hf_at); + } + + return 0; +} + +int ciev_handle(struct at_client *hf_at) +{ + uint32_t index, value; + int ret; + + ret = at_get_number(hf_at, &index); + if (ret < 0) { + LOG_ERR("could not get the Index"); + return ret; + } + /* The first element of the list shall have 1 */ + if (!index) { + LOG_ERR("Invalid index value '0'"); + return 0; + } + + ret = at_get_number(hf_at, &value); + if (ret < 0) { + LOG_ERR("could not get the value"); + return ret; + } + + ag_indicator_handle_values(hf_at, (index - 1), value); + + return 0; +} + +int ring_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) { + LOG_WRN("Invalid call dir (outgoing)"); + } + + if (bt_hf->ring_indication) { + bt_hf->ring_indication(call); + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +int clip_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + char *number; + uint32_t type; + int err; + struct bt_hfp_hf_call *call; + + number = at_get_string(hf_at); + + err = at_get_number(hf_at, &type); + if (err) { + LOG_WRN("could not get the type"); + } else { + type = 0; + } + + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) { + LOG_WRN("Invalid call dir (outgoing)"); + } + + if (bt_hf->clip) { + bt_hf->clip(call, number, (uint8_t)type); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +int vgm_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t gain; + int err; + + err = at_get_number(hf_at, &gain); + if (err) { + LOG_ERR("could not get the microphone gain"); + return err; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid microphone gain (%d > %d)", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + if (bt_hf->vgm) { + bt_hf->vgm(hf, (uint8_t)gain); + } + + return 0; +} + +int vgs_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t gain; + int err; + + err = at_get_number(hf_at, &gain); + if (err) { + LOG_ERR("could not get the speaker gain"); + return err; + } + + if (gain > BT_HFP_HF_VGS_GAIN_MAX) { + LOG_ERR("Invalid speaker gain (%d > %d)", gain, BT_HFP_HF_VGS_GAIN_MAX); + return -EINVAL; + } + + if (bt_hf->vgs) { + bt_hf->vgs(hf, (uint8_t)gain); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bsir_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t inband; + int err; + + err = at_get_number(hf_at, &inband); + if (err) { + LOG_ERR("could not get bsir value"); + return err; + } + + if (inband > 1) { + LOG_ERR("Invalid %d bsir value", inband); + return -EINVAL; + } + + if (bt_hf->inband_ring) { + bt_hf->inband_ring(hf, (bool)inband); + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +int bcs_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t codec_id; + int err; + + err = at_get_number(hf_at, &codec_id); + if (err) { + LOG_ERR("could not get bcs value"); + return err; + } + + if (!(hf->hf_codec_ids & BIT(codec_id))) { + LOG_ERR("Invalid codec id %d", codec_id); + err = bt_hfp_hf_set_codecs(hf, hf->hf_codec_ids); + return err; + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + + if (bt_hf->codec_negotiate) { + bt_hf->codec_negotiate(hf, codec_id); + return 0; + } + + err = bt_hfp_hf_select_codec(hf, codec_id); + return err; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +static int btrh_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t on_hold; + int err; + struct bt_hfp_hf_call *call; + + err = at_get_number(hf_at, &on_hold); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + if (on_hold == BT_HFP_BTRH_ON_HOLD) { + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) { + LOG_WRN("Invalid call dir (outgoing)"); + } + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + } else { + call = get_call_with_state_and_flag(hf, + BT_HFP_HF_CALL_STATE_ACTIVE, BT_HFP_HF_CALL_INCOMING_HELD); + if (!call) { + return 0; + } + + if (on_hold == BT_HFP_BTRH_ACCEPTED) { + atomic_clear_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + if (bt_hf && bt_hf->accept) { + bt_hf->accept(call); + } + } else if (on_hold == BT_HFP_BTRH_REJECTED) { + hf_reject_call(call); + } else { + return -EINVAL; + } + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int ccwa_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + char *number; + uint32_t type; + int err; + struct bt_hfp_hf_call *call; + + number = at_get_string(hf_at); + + err = at_get_number(hf_at, &type); + if (err) { + LOG_WRN("could not get the type"); + } else { + type = 0; + } + + call = get_new_call(hf); + if (!call) { + LOG_ERR("Not available call object"); + return 0; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_INCOMING); + if (bt_hf->call_waiting) { + bt_hf->call_waiting(call, number, (uint8_t)type); + } + + return 0; +} + +static struct _chld_feature { + const char *name; + int value; +} chld_feature_map[] = { + { "1x", BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE }, + { "2x", BT_HFP_CALL_PRIVATE_CNLTN_MODE }, + { "0", BT_HFP_CHLD_RELEASE_ALL }, + { "1", BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER }, + { "2", BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER }, + { "3", BT_HFP_CALL_ACTIVE_HELD }, + { "4", BT_HFP_CALL_QUITE }, +}; + +static int get_chld_feature(const char *name) +{ + struct _chld_feature *chld; + + for (size_t index = 0; index < ARRAY_SIZE(chld_feature_map); index++) { + chld = &chld_feature_map[index]; + if (!strncmp(name, chld->name, strlen(chld->name))) { + return chld->value; + } + } + + return -EINVAL; +} + +int chld_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + char *value; + uint32_t chld_features = 0; + int err; + + /* Parsing Example: CHLD: (0,1,2,3,4) */ + if (at_open_list(hf_at) < 0) { + LOG_ERR("Could not get open list"); + goto error; + } + + while (at_has_next_list(hf_at)) { + value = at_get_raw_string(hf_at, NULL); + if (!value) { + LOG_ERR("Could not get value"); + goto error; + } + + err = get_chld_feature(value); + if (err < 0) { + LOG_ERR("Cannot parse the value %s", value); + goto error; + } + + if (NUM_BITS(sizeof(chld_features)) > err) { + chld_features |= BIT(err); + } + } + + if (at_close_list(hf_at) < 0) { + LOG_ERR("Could not get close list"); + goto error; + } + + if (!((chld_features & BIT(BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER)) && + (chld_features & BIT(BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER)))) { + LOG_ERR("AT+CHLD values 1 and 2 should be supported by AG"); + goto error; + } + + hf->chld_features = chld_features; + + return 0; + +error: + LOG_ERR("Error on AT+CHLD=? response"); + hf_slc_error(hf_at); + return -EINVAL; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +static int cnum_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + char *alpha; + char *number; + uint32_t type; + char *speed; + uint32_t service = 4; + + alpha = at_get_raw_string(hf_at, NULL); + number = at_get_string(hf_at); + if (!number) { + LOG_INF("Cannot get number"); + return -EINVAL; + } + + err = at_get_number(hf_at, &type); + if (err) { + LOG_INF("Cannot get type"); + return -EINVAL; + } + + speed = at_get_raw_string(hf_at, NULL); + + err = at_get_number(hf_at, &service); + if (err) { + LOG_INF("Cannot get service"); + } + + if (bt_hf->subscriber_number) { + bt_hf->subscriber_number(hf, number, (uint8_t)type, (uint8_t)service); + } + + LOG_DBG("CNUM number %s type %d service %d", number, type, service); + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +static int bind_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + uint32_t index; + uint32_t value; + uint32_t ind = 0; + uint32_t ind_enable = hf->ind_enable; + + err = at_open_list(hf_at); + if (!err) { + /* It is a list. */ + while (at_has_next_list(hf_at)) { + err = at_get_number(hf_at, &index); + if (err) { + LOG_INF("Cannot get indicator"); + goto failed; + } + + ind |= BIT(index); + } + + if (at_close_list(hf_at) < 0) { + LOG_ERR("Could not get close list"); + goto failed; + } + + hf->ag_ind = ind; + return 0; + } + + err = at_get_number(hf_at, &index); + if (err) { + LOG_INF("Cannot get indicator"); + goto failed; + } + + err = at_get_number(hf_at, &value); + if (err) { + LOG_INF("Cannot get status"); + goto failed; + } + + if (!value) { + ind_enable &= ~BIT(index); + } else { + ind_enable |= BIT(index); + } + + hf->ind_enable = ind_enable; + return 0; + +failed: + LOG_ERR("Error on AT+BIND response"); + hf_slc_error(hf_at); + return -EINVAL; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +static const struct unsolicited { + const char *cmd; + enum at_cmd_type type; + int (*func)(struct at_client *hf_at); +} handlers[] = { + { "CIEV", AT_CMD_TYPE_UNSOLICITED, ciev_handle }, + { "RING", AT_CMD_TYPE_OTHER, ring_handle }, +#if defined(CONFIG_BT_HFP_HF_CLI) + { "CLIP", AT_CMD_TYPE_UNSOLICITED, clip_handle }, +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + { "VGM", AT_CMD_TYPE_UNSOLICITED, vgm_handle }, + { "VGS", AT_CMD_TYPE_UNSOLICITED, vgs_handle }, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + { "BSIR", AT_CMD_TYPE_UNSOLICITED, bsir_handle }, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + { "BCS", AT_CMD_TYPE_UNSOLICITED, bcs_handle }, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + { "BTRH", AT_CMD_TYPE_UNSOLICITED, btrh_handle }, +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + { "CCWA", AT_CMD_TYPE_UNSOLICITED, ccwa_handle }, + { "CHLD", AT_CMD_TYPE_UNSOLICITED, chld_handle }, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_ECS) + { "CLCC", AT_CMD_TYPE_UNSOLICITED, clcc_handle }, +#endif /* CONFIG_BT_HFP_HF_ECS */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + { "BVRA", AT_CMD_TYPE_UNSOLICITED, bvra_handle }, +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + { "CNUM", AT_CMD_TYPE_UNSOLICITED, cnum_handle }, +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) + { "BIND", AT_CMD_TYPE_UNSOLICITED, bind_handle }, +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ +}; + +static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + if (!strncmp(hf_at->buf, handlers[i].cmd, + strlen(handlers[i].cmd))) { + return &handlers[i]; + } + } + + return NULL; +} + +int unsolicited_cb(struct at_client *hf_at, struct net_buf *buf) +{ + const struct unsolicited *handler; + + handler = hfp_hf_unsol_lookup(hf_at); + if (!handler) { + LOG_ERR("Unhandled unsolicited response"); + return -ENOMSG; + } + + if (!at_parse_cmd_input(hf_at, buf, handler->cmd, handler->func, + handler->type)) { + return 0; + } + + return -ENOMSG; +} + +static int send_at_cmee(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (hf->ag_features & BT_HFP_AG_FEATURE_EXT_ERR) { + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CMEE=1"); + } else { + return -ENOTSUP; + } +} + +static int at_cmee_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CMEE set (result %d) on %p", result, hf); + + return 0; +} + +static int send_at_cops(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+COPS=3,0"); +} + +static int at_cops_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("COPS set (result %d) on %p", result, hf); + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int send_at_clip(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CLIP=1"); +} + +static int at_clip_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CLIP set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int send_at_vgm(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGM=%d", hf->vgm); +} + +static int at_vgm_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGM set (result %d) on %p", result, hf); + + return 0; +} + +static int send_at_vgs(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGS=%d", hf->vgs); +} + +static int at_vgs_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGS set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int send_at_ccwa(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + return -ENOTSUP; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL)) { + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CCWA=1"); +} + +static int at_ccwa_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CCWA set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +typedef int (*at_send_t)(struct bt_hfp_hf *hf, at_finish_cb_t cb); + +static struct at_cmd_init +{ + at_send_t send; + at_finish_cb_t finish; + bool disconnect; /* Disconnect if command failed. */ +} cmd_init_list[] = { +#if defined(CONFIG_BT_HFP_HF_VOLUME) + {send_at_vgm, at_vgm_finish, false}, + {send_at_vgs, at_vgs_finish, false}, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + {send_at_cmee, at_cmee_finish, false}, + {send_at_cops, at_cops_finish, false}, +#if defined(CONFIG_BT_HFP_HF_CLI) + {send_at_clip, at_clip_finish, false}, +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + {send_at_ccwa, at_ccwa_finish, false}, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +}; + +static int at_cmd_init_start(struct bt_hfp_hf *hf); + +static int at_cmd_init_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + at_finish_cb_t finish; + + if (result != AT_RESULT_OK) { + LOG_WRN("It is ERROR response of AT command %d.", hf->cmd_init_seq); + } + + if (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) { + finish = cmd_init_list[hf->cmd_init_seq].finish; + if (finish) { + (void)finish(hf_at, result, cme_err); + } + } else { + LOG_ERR("Invalid indicator (%d>=%d)", hf->cmd_init_seq, + ARRAY_SIZE(cmd_init_list)); + } + + /* Goto next AT command */ + hf->cmd_init_seq++; + (void)at_cmd_init_start(hf); + return 0; +} + +static int at_cmd_init_start(struct bt_hfp_hf *hf) +{ + at_send_t send; + int err = -EINVAL; + + while (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) { + LOG_DBG("Fetch AT command (%d)", hf->cmd_init_seq); + send = cmd_init_list[hf->cmd_init_seq].send; + if (send) { + LOG_DBG("Send AT command"); + err = send(hf, at_cmd_init_finish); + } else { + LOG_WRN("Invalid send func of AT command"); + } + + if (!err) { + break; + } + + LOG_WRN("AT command sending failed"); + if (cmd_init_list[hf->cmd_init_seq].disconnect) { + hfp_hf_send_failed(hf); + break; + } + /* Goto next AT command */ + LOG_WRN("Send next AT command"); + hf->cmd_init_seq++; + } + return err; +} + +static void slc_completed(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_conn *conn = hf->acl; + + if (bt_hf->connected) { + bt_hf->connected(conn, hf); + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); + + /* Start with first AT command */ + hf->cmd_init_seq = 0; + if (at_cmd_init_start(hf)) { + LOG_ERR("Fail to start AT command initialization"); + } +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +static int send_at_bind_status(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND?"); +} + +static int send_at_bind_hf_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + char buffer[4]; + char *bind; + + hf->hf_ind = 0; + + bind = &buffer[0]; + if (IS_ENABLED(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY)) { + *bind = '0' + HFP_HF_ENHANCED_SAFETY_IND; + bind++; + *bind = ','; + bind++; + hf->hf_ind |= BIT(HFP_HF_ENHANCED_SAFETY_IND); + } + + if (IS_ENABLED(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY)) { + *bind = '0' + HFP_HF_BATTERY_LEVEL_IND; + bind++; + *bind = ','; + bind++; + hf->hf_ind |= BIT(HFP_HF_BATTERY_LEVEL_IND); + } + + if (bind <= &buffer[0]) { + return -EINVAL; + } + + bind--; + *bind = '\0'; + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND=%s", buffer); +} + +static int send_at_bind_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND=?"); +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int send_at_chld_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CHLD=?"); +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +static int send_at_cmer(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + at_register_unsolicited(&hf->at, unsolicited_cb); + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CMER=3,0,0,1"); +} + +static int send_at_cind_status(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, cind_status_resp, cb, "AT+CIND?"); +} + +static int send_at_cind_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, cind_resp, cb, "AT+CIND=?"); +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static void get_codec_ids(struct bt_hfp_hf *hf, char *buffer, size_t buffer_len) +{ + size_t len = 0; + uint8_t ids = hf->hf_codec_ids; + int index = 0; + + while (ids && (len < (buffer_len-2))) { + if (ids & 0x01) { + buffer[len++] = index + '0'; + buffer[len++] = ','; + } + index++; + ids = ids >> 1; + } + + if (len > 0) { + len--; + } + + buffer[len] = '\0'; +} + +static int send_at_bac(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; + + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BAC=%s", ids); +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +static int send_at_brsf(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, brsf_resp, cb, "AT+BRSF=%u", hf->hf_features); +} + +static struct slc_init +{ + at_send_t send; + bool disconnect; /* Disconnect if command failed. */ + uint32_t ag_feature_mask; /* AG feature mask */ +} slc_init_list[] = { + {send_at_brsf, true, 0}, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + {send_at_bac, true, BT_HFP_AG_FEATURE_CODEC_NEG}, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + {send_at_cind_supported, true, 0}, + {send_at_cind_status, true, 0}, + {send_at_cmer, true, 0}, +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + {send_at_chld_supported, true, BT_HFP_AG_FEATURE_3WAY_CALL}, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) + {send_at_bind_hf_supported, true, BT_HFP_AG_FEATURE_HF_IND}, + {send_at_bind_supported, true, BT_HFP_AG_FEATURE_HF_IND}, + {send_at_bind_status, true, BT_HFP_AG_FEATURE_HF_IND}, +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ +}; + +static int slc_init_start(struct bt_hfp_hf *hf); + +static int slc_init_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + if (result != AT_RESULT_OK) { + LOG_WRN("It is ERROR response of AT command %d.", hf->cmd_init_seq); + if (slc_init_list[hf->cmd_init_seq].disconnect) { + hf_slc_error(&hf->at); + return 0; + } + } + + if (ARRAY_SIZE(slc_init_list) <= hf->cmd_init_seq) { + LOG_ERR("Invalid indicator (%d>=%d)", hf->cmd_init_seq, + ARRAY_SIZE(slc_init_list)); + } + + /* Goto next AT command */ + hf->cmd_init_seq++; + (void)slc_init_start(hf); + return 0; +} + +static int slc_init_start(struct bt_hfp_hf *hf) +{ + at_send_t send; + uint32_t feture_mask; + int err = -EINVAL; + + while (ARRAY_SIZE(slc_init_list) > hf->cmd_init_seq) { + LOG_DBG("Fetch AT command (%d)", hf->cmd_init_seq); + feture_mask = slc_init_list[hf->cmd_init_seq].ag_feature_mask; + if (feture_mask && (!(feture_mask & hf->ag_features))) { + /* The feature is not supported by AG. Skip the step. */ + LOG_INF("Skip SLC init step %d", hf->cmd_init_seq); + hf->cmd_init_seq++; + continue; + } + + send = slc_init_list[hf->cmd_init_seq].send; + if (send) { + LOG_DBG("Send AT command"); + err = send(hf, slc_init_finish); + } else { + LOG_WRN("Invalid send func of AT command"); + } + + if (!err) { + break; + } + + LOG_WRN("AT command sending failed"); + if (slc_init_list[hf->cmd_init_seq].disconnect) { + hfp_hf_send_failed(hf); + break; + } + /* Goto next AT command */ + LOG_WRN("Send next AT command"); + hf->cmd_init_seq++; + } + + if (ARRAY_SIZE(slc_init_list) <= hf->cmd_init_seq) { + slc_completed(&hf->at); + } + + return err; +} + +int hf_slc_establish(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + hf->cmd_init_seq = 0; + err = slc_init_start(hf); + if (err < 0) { + hf_slc_error(&hf->at); + return err; + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int cli_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CLIP set (result %d) on %p", result, hf); + + /* AT+CLI is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_CLI) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, cli_finish, "AT+CLIP=%d", enable ? 1 : 0); + if (err < 0) { + LOG_ERR("HFP HF CLI set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CLI */ +} + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int vgm_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGM set (result %d) on %p", result, hf); + + /* AT+VGM is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain) +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + hf->vgm = gain; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, NULL, vgm_finish, "AT+VGM=%d", gain); + if (err < 0) { + LOG_ERR("HFP HF VGM set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOLUME */ +} + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int vgs_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGS set (result %d) on %p", result, hf); + + /* AT+VGS is done. */ + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain) +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + hf->vgs = gain; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, NULL, vgs_finish, "AT+VGS=%d", gain); + if (err < 0) { + LOG_ERR("HFP HF VGS set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOLUME */ +} + +static int cops_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t mode; + uint32_t format; + char *operator; + int err; + + err = at_get_number(hf_at, &mode); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + err = at_get_number(hf_at, &format); + if (err < 0) { + LOG_ERR("Error getting value"); + return err; + } + + operator = at_get_string(hf_at); + + if (bt_hf && bt_hf->operator) { + bt_hf->operator(hf, (uint8_t)mode, (uint8_t)format, operator); + } + + return 0; +} + +static int cops_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + LOG_DBG(""); + + err = at_parse_cmd_input(hf_at, buf, "COPS", cops_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) { + LOG_ERR("Cannot parse response of AT+COPS?"); + return err; + } + + return 0; +} + +static int cops_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+COPS? (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + err = hfp_hf_send_cmd(hf, cops_resp, cops_finish, "AT+COPS?"); + if (err < 0) { + LOG_ERR("Fail to read the currently selected operator on %p", hf); + } + + return err; +} + +static int binp_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + char *number; + + number = at_get_string(hf_at); + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_BINP); + + if (bt_hf && bt_hf->request_phone_number) { + bt_hf->request_phone_number(hf, number); + } + + return 0; +} + +static int binp_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + LOG_DBG(""); + + err = at_parse_cmd_input(hf_at, buf, "BINP", binp_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) { + LOG_ERR("Cannot parse response of AT+BINP=1"); + return err; + } + + return 0; +} + +static int binp_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BINP=1 (result %d) on %p", result, hf); + + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_BINP)) { + if (bt_hf && bt_hf->request_phone_number) { + bt_hf->request_phone_number(hf, NULL); + } + } + + return 0; +} + +int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + return 0; + } + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_BINP); + + err = hfp_hf_send_cmd(hf, binp_resp, binp_finish, "AT+BINP=1"); + if (err < 0) { + LOG_ERR("Fail to request phone number to the AG on %p", hf); + } + + return err; +} + +static int vts_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+VTS (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -ENOTCONN; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING) || + (!(!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)))) { + LOG_ERR("Invalid call status"); + return -EINVAL; + } + + if (!IS_VALID_DTMF(code)) { + LOG_ERR("Invalid code"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, vts_finish, "AT+VTS=%c", code); + if (err < 0) { + LOG_ERR("Fail to tramsit DTMF Codes on %p", hf); + } + + return err; +} + +static int cnum_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CNUM (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_query_subscriber(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + err = hfp_hf_send_cmd(hf, NULL, cnum_finish, "AT+CNUM"); + if (err < 0) { + LOG_ERR("Fail to query subscriber number information on %p", hf); + } + + return err; +} + +static int bia_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIA (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_indicator_status(struct bt_hfp_hf *hf, uint8_t status) +{ + int err; + size_t index; + char buffer[HF_MAX_AG_INDICATORS * 2 + 1]; + char *bia_status; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + bia_status = &buffer[0]; + for (index = 0; index < ARRAY_SIZE(hf->ind_table); index++) { + if ((hf->ind_table[index] != -1) && (index < NUM_BITS(sizeof(status)))) { + if (status & BIT(hf->ind_table[index])) { + *bia_status = '1'; + } else { + *bia_status = '0'; + } + bia_status++; + *bia_status = ','; + bia_status++; + } else { + break; + } + } + + if (bia_status <= &buffer[0]) { + LOG_ERR("Not found valid AG indicator on %p", hf); + return -EINVAL; + } + + bia_status--; + *bia_status = '\0'; + + err = hfp_hf_send_cmd(hf, NULL, bia_finish, "AT+BIA=%s", buffer); + if (err < 0) { + LOG_ERR("Fail to activated/deactivated AG indicators on %p", hf); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) +static int biev_enh_safety_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIEV (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ + +int bt_hfp_hf_enhanced_safety(struct bt_hfp_hf *hf, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!((hf->hf_ind & BIT(HFP_HF_ENHANCED_SAFETY_IND)) && + (hf->ag_ind & BIT(HFP_HF_ENHANCED_SAFETY_IND)))) { + LOG_ERR("The indicator is unsupported"); + return -ENOTSUP; + } + + if (!(hf->ind_enable & BIT(HFP_HF_ENHANCED_SAFETY_IND))) { + LOG_ERR("The indicator is disabled"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, biev_enh_safety_finish, "AT+BIEV=%d,%d", + HFP_HF_ENHANCED_SAFETY_IND, enable ? 1 : 0); + if (err < 0) { + LOG_ERR("Fail to transfer enhanced safety value on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) +static int biev_battery_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIEV (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ + +int bt_hfp_hf_battery(struct bt_hfp_hf *hf, uint8_t level) +{ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!((hf->hf_ind & BIT(HFP_HF_BATTERY_LEVEL_IND)) && + (hf->ag_ind & BIT(HFP_HF_BATTERY_LEVEL_IND)))) { + LOG_ERR("The indicator is unsupported"); + return -ENOTSUP; + } + + if (!(hf->ind_enable & BIT(HFP_HF_BATTERY_LEVEL_IND))) { + LOG_ERR("The indicator is disabled"); + return -EINVAL; + } + + if (!IS_VALID_BATTERY_LEVEL(level)) { + LOG_ERR("Invalid battery level %d", level); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, biev_battery_finish, "AT+BIEV=%d,%d", + HFP_HF_BATTERY_LEVEL_IND, level); + if (err < 0) { + LOG_ERR("Fail to transfer remaining battery level on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ +} + +static int ata_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("ATA (result %d) on %p", result, hf); + + return 0; +} + +static int btrh_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BTRH (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_accept(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING) { + err = hfp_hf_send_cmd(hf, NULL, ata_finish, "ATA"); + if (err < 0) { + LOG_ERR("Fail to accept the incoming call on %p", hf); + } + return err; + } + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) { + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_ACCEPTED); + if (err < 0) { + LOG_ERR("Fail to accept the held incoming call on %p", hf); + } + return err; + } + + return -EINVAL; +} + +static int chup_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHUP (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_reject(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_REJECT_CALL)) { + LOG_ERR("AG has not ability to reject call"); + return -ENOTSUP; + } + + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING) { + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) { + LOG_ERR("Fail to reject the incoming call on %p", hf); + } + return err; + } + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) { + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_REJECTED); + if (err < 0) { + LOG_ERR("Fail to reject the held incoming call on %p", hf); + } + return err; + } + + return -EINVAL; +} + +int bt_hfp_hf_terminate(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (atomic_get(call->state) == BT_HFP_HF_CALL_STATE_HELD) { + LOG_ERR("Held call cannot be terminated"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) { + LOG_ERR("Fail to terminate the none held call on %p", hf); + } + + return err; +} + +int bt_hfp_hf_hold_incoming(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (!(atomic_get(call->state) == BT_HFP_HF_CALL_STATE_WAITING)) { + LOG_ERR("No incoming call setup in progress"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_ON_HOLD); + if (err < 0) { + LOG_ERR("Fail to hold the incoming call on %p", hf); + } + + return err; +} + +static int query_btrh_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + struct bt_hfp_hf_call *call; + uint32_t value; + + err = at_get_number(hf_at, &value); + if (err < 0) { + LOG_ERR("Cannot get value"); + return err; + } + + if (value) { + LOG_ERR("Only support value 0"); + return 0; + } + + call = get_call_with_flag(hf, BT_HFP_HF_CALL_INCOMING_HELD); + if (!call) { + LOG_ERR("Held incoming call is not found"); + return -EINVAL; + } + + if (bt_hf->incoming_held) { + bt_hf->incoming_held(call); + } + + return 0; +} + +static int query_btrh_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + err = at_parse_cmd_input(hf_at, buf, "BTRH", query_btrh_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) { + LOG_ERR("Error parsing CMD input"); + hf_slc_error(hf_at); + } + + return 0; +} + +static int query_btrh_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BTRH? (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + err = hfp_hf_send_cmd(hf, query_btrh_resp, query_btrh_finish, "AT+BTRH?"); + if (err < 0) { + LOG_ERR("Fail to query respond and hold status of AG on %p", hf); + } + + return err; +} + +static int bt_hfp_ag_get_cme_err(enum at_cme cme_err) +{ + int err; + + switch (cme_err) { + case CME_ERROR_OPERATION_NOT_SUPPORTED: + err = -EOPNOTSUPP; + break; + case CME_ERROR_AG_FAILURE: + err = -EFAULT; + break; + case CME_ERROR_MEMORY_FAILURE: + err = -ENOSR; + break; + case CME_ERROR_MEMORY_FULL: + err = -ENOMEM; + break; + case CME_ERROR_DIAL_STRING_TO_LONG: + err = -ENAMETOOLONG; + break; + case CME_ERROR_INVALID_INDEX: + err = -EINVAL; + break; + case CME_ERROR_OPERATION_NOT_ALLOWED: + err = -ENOTSUP; + break; + case CME_ERROR_NO_CONNECTION_TO_PHONE: + err = -ENOTCONN; + break; + default: + err = -ENOTSUP; + break; + } + + return err; +} + +static int atd_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + struct bt_hfp_hf_call *call; + + LOG_DBG("ATD (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; + } + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) { + return -EINVAL; + } + + if (err) { + free_call(call); + } + + if (bt_hf && bt_hf->dialing) { + bt_hf->dialing(hf, err); + } + return 0; +} + +int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number) +{ + struct bt_hfp_hf_call *call; + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + call = get_dialing_call(hf); + if (call) { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; + } + + call = get_new_call(hf); + if (!call) { + LOG_ERR("Not available call object"); + return -ENOMEM; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD%s", number); + if (err < 0) { + LOG_ERR("Fail to start phone number call on %p", hf); + } + + return err; +} + +int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location) +{ + struct bt_hfp_hf_call *call; + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + call = get_dialing_call(hf); + if (call) { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; + } + + call = get_new_call(hf); + if (!call) { + LOG_ERR("Not available call object"); + return -ENOMEM; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD>%s", location); + if (err < 0) { + LOG_ERR("Fail to last number re-Dial on %p", hf); + } + + return err; +} + +static int bldn_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + struct bt_hfp_hf_call *call; + + LOG_DBG("AT+BLDN (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; + } + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) { + return -EINVAL; + } + + if (err) { + free_call(call); + } + + if (bt_hf && bt_hf->dialing) { + bt_hf->dialing(hf, err); + } + return 0; +} + +int bt_hfp_hf_redial(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + call = get_dialing_call(hf); + if (call) { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; + } + + call = get_new_call(hf); + if (!call) { + LOG_ERR("Not available call object"); + return -ENOMEM; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, bldn_finish, "AT+BLDN"); + if (err < 0) { + LOG_ERR("Fail to start memory dialing on %p", hf); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bcc_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BCC (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + int err; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (hf->chan.sco) { + LOG_ERR("Audio conenction has been connected"); + return -ECONNREFUSED; + } + + err = hfp_hf_send_cmd(hf, NULL, bcc_finish, "AT+BCC"); + if (err < 0) { + LOG_ERR("Fail to setup audio connection on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bcs_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BCS (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id) +{ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->hf_codec_ids & BIT(codec_id))) { + LOG_ERR("Codec ID is unsupported"); + return -ENOTSUP; + } + + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN)) { + LOG_ERR("Invalid context"); + return -ESRCH; + } + + return hfp_hf_send_cmd(hf, NULL, bcs_finish, "AT+BCS=%d", codec_id); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bac_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BAC (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids) +{ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + char ids[sizeof(hf->hf_codec_ids)*2*8 + 1]; + + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (codec_ids & BT_HFP_HF_CODEC_CVSD) { + LOG_ERR("CVSD should be supported"); + return -EINVAL; + } + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + hf->hf_codec_ids = codec_ids; + + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, bac_finish, "AT+BAC=%s", ids); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + +#if defined(CONFIG_BT_HFP_HF_ECNR) +static int nrec_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + LOG_DBG("AT+NREC=0 (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) { + err = bt_hfp_ag_get_cme_err(cme_err); + } else if (result == AT_RESULT_ERROR) { + err = -ENOTSUP; + } else { + err = 0; + } + + if (bt_hf && bt_hf->ecnr_turn_off) { + bt_hf->ecnr_turn_off(hf, err); + } + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECNR */ + +int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_ECNR) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECNR)) { + LOG_ERR("EC and/or NR functions is unsupported by AG"); + return -ENOTSUP; + } + + if (hf->chan.sco) { + LOG_ERR("Audio conenction has been connected"); + return -EBUSY; + } + + return hfp_hf_send_cmd(hf, NULL, nrec_finish, "AT+NREC=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ECNR */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int ccwa_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CCWA (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_call_waiting_notify(struct bt_hfp_hf *hf, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, ccwa_finish, "AT+CCWA=%d", enable ? 1 : 0); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_release_all_held_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=0 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_release_all_held(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CHLD_RELEASE_ALL))) { + LOG_ERR("Releasing all held calls is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_release_all_held_finish, "AT+CHLD=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_set_udub_finish(struct at_client *hf_at, enum at_result result, + enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=0 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_set_udub(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } - return 0; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CHLD_RELEASE_ALL))) { + LOG_ERR("UDUB is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_set_udub_finish, "AT+CHLD=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ } -int cind_status_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_release_active_accept_other_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; + LOG_DBG("AT+CHLD=1 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_release_active_accept_other(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; } - at_register_unsolicited(hf_at, unsolicited_cb); - err = hfp_hf_send_cmd(hf, NULL, cmer_finish, "AT+CMER=3,0,0,1"); - if (err < 0) { - hf_slc_error(hf_at); - return err; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; } + return hfp_hf_send_cmd(hf, NULL, chld_release_active_accept_other_finish, + "AT+CHLD=1"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_hold_active_accept_other_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=2 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + return 0; } +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_hold_active_accept_other(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_hold_active_accept_other_finish, + "AT+CHLD=2"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} -int cind_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_join_conversation_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; + LOG_DBG("AT+CHLD=3 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_join_conversation(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; } - err = hfp_hf_send_cmd(hf, cind_status_resp, cind_status_finish, - "AT+CIND?"); - if (err < 0) { - hf_slc_error(hf_at); - return err; + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_ACTIVE_HELD))) { + LOG_ERR("Adding a held call to the conversation is unsupported by AG"); + return -ENOTSUP; } + return hfp_hf_send_cmd(hf, NULL, chld_join_conversation_finish, + "AT+CHLD=3"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_explicit_call_transfer_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=4 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + return 0; } +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_explicit_call_transfer(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_QUITE))) { + LOG_ERR("Expliciting Call Transfer is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_explicit_call_transfer_finish, + "AT+CHLD=4"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} -int brsf_finish(struct at_client *hf_at, enum at_result result, - enum at_cme cme_err) +#if defined(CONFIG_BT_HFP_HF_ECC) +static int chld_release_specified_call_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - int err; - if (result != AT_RESULT_OK) { - LOG_ERR("SLC Connection ERROR in response"); - hf_slc_error(hf_at); - return -EINVAL; + LOG_DBG("AT+CHLD=1 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECC */ + +int bt_hfp_hf_release_specified_call(struct bt_hfp_hf_call *call) +{ +#if defined(CONFIG_BT_HFP_HF_ECC) + struct bt_hfp_hf *hf; + + LOG_DBG(""); + + if (!call) { + LOG_ERR("Invalid call"); + return -ENOTCONN; } - err = hfp_hf_send_cmd(hf, cind_resp, cind_finish, "AT+CIND=?"); - if (err < 0) { - hf_slc_error(hf_at); - return err; + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECC)) { + LOG_ERR("Enhanced Call Control is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECC)) { + LOG_ERR("Enhanced Call Control is unsupported by HF"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE))) { + LOG_ERR("Releasing a specific active call is unsupported by AG"); + return -ENOTSUP; + } + + if (!call->index) { + LOG_ERR("Invalid call index"); + return -EINVAL; } + return hfp_hf_send_cmd(hf, NULL, chld_release_specified_call_finish, + "AT+CHLD=1%d", call->index); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ECC */ +} + +#if defined(CONFIG_BT_HFP_HF_ECC) +static int chld_private_consultation_mode_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=2 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, K_MSEC(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + return 0; } +#endif /* CONFIG_BT_HFP_HF_ECC */ -int hf_slc_establish(struct bt_hfp_hf *hf) +int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call) { - int err; +#if defined(CONFIG_BT_HFP_HF_ECC) + struct bt_hfp_hf *hf; LOG_DBG(""); - err = hfp_hf_send_cmd(hf, brsf_resp, brsf_finish, "AT+BRSF=%u", - hf->hf_features); - if (err < 0) { - hf_slc_error(&hf->at); - return err; + if (!call) { + LOG_ERR("Invalid call"); + return -ENOTCONN; + } + + hf = call->hf; + if (!hf) { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECC)) { + LOG_ERR("Enhanced Call Control is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECC)) { + LOG_ERR("Enhanced Call Control is unsupported by HF"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_PRIVATE_CNLTN_MODE))) { + LOG_ERR("Private Consultation Mode is unsupported by AG"); + return -ENOTSUP; + } + + if (!call->index) { + LOG_ERR("Invalid call index"); + return -EINVAL; + } + + return hfp_hf_send_cmd(hf, NULL, chld_private_consultation_mode_finish, + "AT+CHLD=2%d", call->index); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ECC */ +} + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +static int bvra_1_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BVRA=1 (result %d) on %p", result, hf); + + if (result == AT_RESULT_OK) { + if (!atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + if (bt_hf->voice_recognition) { + bt_hf->voice_recognition(hf, true); + } + } } return 0; } -static struct bt_hfp_hf *bt_hfp_hf_lookup_bt_conn(struct bt_conn *conn) +static int bvra_0_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) { - int i; + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); - for (i = 0; i < ARRAY_SIZE(bt_hfp_hf_pool); i++) { - struct bt_hfp_hf *hf = &bt_hfp_hf_pool[i]; + LOG_DBG("AT+BVRA=0 (result %d) on %p", result, hf); - if (hf->acl == conn) { - return hf; + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + if (bt_hf->voice_recognition) { + bt_hf->voice_recognition(hf, false); } } - return NULL; + return 0; } +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ -int bt_hfp_hf_send_cmd(struct bt_conn *conn, enum bt_hfp_hf_at_cmd cmd) +int bt_hfp_hf_voice_recognition(struct bt_hfp_hf *hf, bool activate) { - struct bt_hfp_hf *hf; - int err; +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + at_finish_cb_t finish; LOG_DBG(""); - if (!conn) { - LOG_ERR("Invalid connection"); + if (!hf) { + LOG_ERR("No HF connection found"); return -ENOTCONN; } - hf = bt_hfp_hf_lookup_bt_conn(conn); + if (!(hf->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG)) { + LOG_ERR("Voice recognition is unsupported by AG"); + return -ENOTSUP; + } + + if (activate) { + finish = bvra_1_finish; + } else { + finish = bvra_0_finish; + } + + return hfp_hf_send_cmd(hf, NULL, finish, "AT+BVRA=%d", + activate ? 1 : 0); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ +} + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +static int bvra_2_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BVRA=2 (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +int bt_hfp_hf_ready_to_accept_audio(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + LOG_DBG(""); + if (!hf) { LOG_ERR("No HF connection found"); return -ENOTCONN; } - switch (cmd) { - case BT_HFP_HF_ATA: - err = hfp_hf_send_cmd(hf, NULL, cmd_complete, "ATA"); - if (err < 0) { - LOG_ERR("Failed ATA"); - return err; - } - break; - case BT_HFP_HF_AT_CHUP: - err = hfp_hf_send_cmd(hf, NULL, cmd_complete, "AT+CHUP"); - if (err < 0) { - LOG_ERR("Failed AT+CHUP"); - return err; - } - break; - default: - LOG_ERR("Invalid AT Command"); + if (!(hf->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG)) { + LOG_ERR("Voice recognition is unsupported by AG"); + return -ENOTSUP; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) { + LOG_ERR("Voice recognition is not activated"); return -EINVAL; } - return 0; + if (!hf->chan.sco) { + LOG_ERR("SCO channel is not ready"); + return -ENOTCONN; + } + + return hfp_hf_send_cmd(hf, NULL, bvra_2_finish, "AT+BVRA=2"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ } static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) @@ -694,21 +3919,27 @@ static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) static void hfp_hf_disconnected(struct bt_rfcomm_dlc *dlc) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); - struct bt_conn *conn = hf->acl; LOG_DBG("hf disconnected!"); if (bt_hf->disconnected) { - bt_hf->disconnected(conn); + bt_hf->disconnected(hf); } + + k_work_cancel(&hf->work); + k_work_cancel_delayable(&hf->deferred_work); + hf->acl = NULL; } static void hfp_hf_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) { struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING); if (at_parse_input(&hf->at, buf) < 0) { LOG_ERR("Parsing failed"); } + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING); + k_work_submit(&hf->work); } static void hfp_hf_sent(struct bt_rfcomm_dlc *dlc, int err) @@ -716,55 +3947,83 @@ static void hfp_hf_sent(struct bt_rfcomm_dlc *dlc, int err) LOG_DBG("DLC %p sent cb (err %d)", dlc, err); } -static int bt_hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, - struct bt_rfcomm_dlc **dlc) +static void bt_hf_work(struct k_work *work) { - int i; + struct bt_hfp_hf *hf = CONTAINER_OF(work, struct bt_hfp_hf, work); + + hfp_hf_send_data(hf); +} + +static struct bt_hfp_hf *hfp_hf_create(struct bt_conn *conn) +{ + size_t index; static struct bt_rfcomm_dlc_ops ops = { .connected = hfp_hf_connected, .disconnected = hfp_hf_disconnected, .recv = hfp_hf_recv, .sent = hfp_hf_sent, }; + struct bt_hfp_hf *hf; LOG_DBG("conn %p", conn); - for (i = 0; i < ARRAY_SIZE(bt_hfp_hf_pool); i++) { - struct bt_hfp_hf *hf = &bt_hfp_hf_pool[i]; - int j; + index = (size_t)bt_conn_index(conn); + hf = &bt_hfp_hf_pool[index]; + if (hf->acl) { + LOG_ERR("HF connection (%p) is established", conn); + return NULL; + } - if (hf->rfcomm_dlc.session) { - continue; - } + memset(hf, 0, sizeof(*hf)); - hf->acl = conn; - hf->at.buf = hf->hf_buffer; - hf->at.buf_max_len = HF_MAX_BUF_LEN; + hf->acl = conn; + hf->at.buf = hf->hf_buffer; + hf->at.buf_max_len = HF_MAX_BUF_LEN; - hf->rfcomm_dlc.ops = &ops; - hf->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; + hf->rfcomm_dlc.ops = &ops; + hf->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; - *dlc = &hf->rfcomm_dlc; + /* Set the supported features*/ + hf->hf_features = BT_HFP_HF_SUPPORTED_FEATURES; - /* Set the supported features*/ - hf->hf_features = BT_HFP_HF_SUPPORTED_FEATURES; + /* Set supported codec ids */ + hf->hf_codec_ids = BT_HFP_HF_SUPPORTED_CODEC_IDS; - for (j = 0; j < HF_MAX_AG_INDICATORS; j++) { - hf->ind_table[j] = -1; - } + k_fifo_init(&hf->tx_pending); - return 0; + k_work_init(&hf->work, bt_hf_work); + + k_work_init_delayable(&hf->deferred_work, bt_hf_deferred_work); + + for (index = 0; index < ARRAY_SIZE(hf->ind_table); index++) { + hf->ind_table[index] = -1; + } + + return hf; +} + +static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + struct bt_hfp_hf *hf; + + hf = hfp_hf_create(conn); + + if (!hf) { + return -ECONNREFUSED; } - LOG_ERR("Unable to establish HF connection (%p)", conn); + *dlc = &hf->rfcomm_dlc; - return -ENOMEM; + return 0; } static void hfp_hf_sco_connected(struct bt_sco_chan *chan) { + struct bt_hfp_hf *hf = CONTAINER_OF(chan, struct bt_hfp_hf, chan); + if ((bt_hf != NULL) && (bt_hf->sco_connected)) { - bt_hf->sco_connected(chan->sco->sco.acl, chan->sco); + bt_hf->sco_connected(hf, chan->sco); } } @@ -778,42 +4037,34 @@ static void hfp_hf_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason) static int bt_hfp_hf_sco_accept(const struct bt_sco_accept_info *info, struct bt_sco_chan **chan) { - int i; static struct bt_sco_chan_ops ops = { .connected = hfp_hf_sco_connected, .disconnected = hfp_hf_sco_disconnected, }; + size_t index; + struct bt_hfp_hf *hf; LOG_DBG("conn %p", info->acl); - for (i = 0; i < ARRAY_SIZE(bt_hfp_hf_pool); i++) { - struct bt_hfp_hf *hf = &bt_hfp_hf_pool[i]; - - if (NULL == hf->rfcomm_dlc.session) { - continue; - } - - if (info->acl != hf->rfcomm_dlc.session->br_chan.chan.conn) { - continue; - } - - hf->chan.ops = &ops; - - *chan = &hf->chan; - - return 0; + index = (size_t)bt_conn_index(info->acl); + hf = &bt_hfp_hf_pool[index]; + if (hf->acl != info->acl) { + LOG_ERR("ACL %p of HF is unaligned with SCO's %p", hf->acl, info->acl); + return -EINVAL; } - LOG_ERR("Unable to establish HF connection (%p)", info->acl); + hf->chan.ops = &ops; + + *chan = &hf->chan; - return -ENOMEM; + return 0; } static void hfp_hf_init(void) { static struct bt_rfcomm_server chan = { .channel = BT_RFCOMM_CHAN_HFP_HF, - .accept = bt_hfp_hf_accept, + .accept = hfp_hf_accept, }; bt_rfcomm_server_register(&chan); @@ -844,3 +4095,43 @@ int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb) return 0; } + +int bt_hfp_hf_connect(struct bt_conn *conn, struct bt_hfp_hf **hf, uint8_t channel) +{ + struct bt_hfp_hf *new_hf; + int err; + + if (!conn || !hf || !channel) { + return -EINVAL; + } + + if (!bt_hf) { + return -EFAULT; + } + + new_hf = hfp_hf_create(conn); + if (!new_hf) { + return -ECONNREFUSED; + } + + err = bt_rfcomm_dlc_connect(conn, &new_hf->rfcomm_dlc, channel); + if (err != 0) { + (void)memset(new_hf, 0, sizeof(*new_hf)); + *hf = NULL; + } else { + *hf = new_hf; + } + + return err; +} + +int bt_hfp_hf_disconnect(struct bt_hfp_hf *hf) +{ + LOG_DBG(""); + + if (!hf) { + return -EINVAL; + } + + return bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc); +} diff --git a/subsys/bluetooth/host/classic/hfp_hf_internal.h b/subsys/bluetooth/host/classic/hfp_hf_internal.h new file mode 100644 index 0000000000000..caecc60dac9fa --- /dev/null +++ b/subsys/bluetooth/host/classic/hfp_hf_internal.h @@ -0,0 +1,223 @@ +/** @file + * @brief Internal APIs for Bluetooth Handsfree profile handling. + */ + +/* + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "hfp_internal.h" + +#if defined(CONFIG_BT_HFP_HF_CLI) +#define BT_HFP_HF_FEATURE_CLI_ENABLE BT_HFP_HF_FEATURE_CLI +#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE BT_HFP_HF_SDP_FEATURE_CLI +#else +#define BT_HFP_HF_FEATURE_CLI_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +#define BT_HFP_HF_FEATURE_VOLUME_ENABLE BT_HFP_HF_FEATURE_VOLUME +#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE BT_HFP_HF_SDP_FEATURE_VOLUME +#else +#define BT_HFP_HF_FEATURE_VOLUME_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +#define BT_HFP_HF_CODEC_NEG_ENABLE BT_HFP_HF_FEATURE_CODEC_NEG +#else +#define BT_HFP_HF_CODEC_NEG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_ECNR) +#define BT_HFP_HF_FEATURE_ECNR_ENABLE BT_HFP_HF_FEATURE_ECNR +#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE BT_HFP_HF_SDP_FEATURE_ECNR +#else +#define BT_HFP_HF_FEATURE_ECNR_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_FEATURE_3WAY_CALL +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_SDP_FEATURE_3WAY_CALL +#else +#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_HF_ECS) +#define BT_HFP_HF_FEATURE_ECS_ENABLE BT_HFP_HF_FEATURE_ECS +#else +#define BT_HFP_HF_FEATURE_ECS_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ECS */ + +#if defined(CONFIG_BT_HFP_HF_ECC) +#define BT_HFP_HF_FEATURE_ECC_ENABLE BT_HFP_HF_FEATURE_ECC +#else +#define BT_HFP_HF_FEATURE_ECC_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ECC */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG +#else +#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_ENH_VOICE_RECG +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG +#else +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG_TEXT +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT +#else +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +#define BT_HFP_HF_FEATURE_HF_IND_ENABLE BT_HFP_HF_FEATURE_HF_IND +#else +#define BT_HFP_HF_FEATURE_HF_IND_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +/* HFP HF Supported features */ +#define BT_HFP_HF_SUPPORTED_FEATURES (\ + BT_HFP_HF_FEATURE_CLI_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE |\ + BT_HFP_HF_CODEC_NEG_ENABLE | \ + BT_HFP_HF_FEATURE_ECNR_ENABLE | \ + BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_HF_FEATURE_ECS_ENABLE | \ + BT_HFP_HF_FEATURE_ECC_ENABLE | \ + BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_HF_FEATURE_HF_IND_ENABLE) + +/* HFP HF Supported features in SDP */ +#define BT_HFP_HF_SDP_SUPPORTED_FEATURES (\ + BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE) + +#define BT_HFP_HF_CODEC_CVSD_MASK BIT(BT_HFP_HF_CODEC_CVSD) + +#if defined(CONFIG_BT_HFP_HF_CODEC_MSBC) +#define BT_HFP_HF_CODEC_MSBC_ENABLE BIT(BT_HFP_HF_CODEC_MSBC) +#else +#define BT_HFP_HF_CODEC_MSBC_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_MSBC */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_LC3_SWB) +#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE BIT(BT_HFP_HF_CODEC_LC3_SWB) +#else +#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_LC3_SWB */ + +/* HFP HF Supported Codec IDs*/ +#define BT_HFP_HF_SUPPORTED_CODEC_IDS \ + BT_HFP_HF_CODEC_CVSD_MASK | \ + BT_HFP_HF_CODEC_MSBC_ENABLE | \ + BT_HFP_HF_CODEC_LC3_SWB_ENABLE + +/* bt_hfp_hf flags: the flags defined here represent hfp hf parameters */ +enum { + BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ + BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ + BT_HFP_HF_FLAG_RX_ONGOING, /* HFP HF RX is ongoing */ + BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ + BT_HFP_HF_FLAG_CLCC_PENDING, /* HFP HF CLCC is pending */ + BT_HFP_HF_FLAG_VRE_ACTIVATE, /* VRE is activated */ + BT_HFP_HF_FLAG_BINP, /* +BINP result code is received */ + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_NUM_FLAGS, +}; + +/* bt_hfp_hf_call flags: the flags defined here represent hfp hf call parameters */ +enum { + BT_HFP_HF_CALL_IN_USING, /* Object is in using */ + BT_HFP_HF_CALL_CLCC, /* CLCC report received */ + BT_HFP_HF_CALL_INCOMING, /* Incoming call */ + BT_HFP_HF_CALL_INCOMING_HELD, /* Incoming call held */ + BT_HFP_HF_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */ + BT_HFP_HF_CALL_INCOMING_3WAY, /* Incoming 3 way call */ + + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_CALL_NUM_FLAGS, +}; + +/* bt_hfp_hf_call state: the flags defined here represent hfp hf call state parameters */ +enum { + /* Call state flags */ + BT_HFP_HF_CALL_STATE_TERMINATE, /* Call terminate */ + BT_HFP_HF_CALL_STATE_OUTGOING, /* Call outgoing */ + BT_HFP_HF_CALL_STATE_INCOMING, /* Call incoming */ + BT_HFP_HF_CALL_STATE_ALERTING, /* Call alerting */ + BT_HFP_HF_CALL_STATE_WAITING, /* Call waiting */ + BT_HFP_HF_CALL_STATE_ACTIVE, /* Call active */ + BT_HFP_HF_CALL_STATE_HELD, /* Call held */ + + /* Total number of flags - must be at the end of the enum */ + BT_HFP_HF_CALL_STATE_NUM_FLAGS, +}; + +struct bt_hfp_hf_call { + struct bt_hfp_hf *hf; + uint8_t index; + + ATOMIC_DEFINE(flags, BT_HFP_HF_CALL_NUM_FLAGS); + ATOMIC_DEFINE(state, BT_HFP_HF_CALL_STATE_NUM_FLAGS); +}; + +struct bt_hfp_hf { + struct bt_rfcomm_dlc rfcomm_dlc; + /* ACL connection handle */ + struct bt_conn *acl; + /* AT command sending queue */ + at_finish_cb_t backup_finish; + struct k_fifo tx_pending; + /* SCO Channel */ + struct bt_sco_chan chan; + char hf_buffer[HF_MAX_BUF_LEN]; + struct at_client at; + uint32_t hf_features; + uint32_t ag_features; + uint8_t hf_codec_ids; + uint8_t vgm; + uint8_t vgs; + int8_t ind_table[HF_MAX_AG_INDICATORS]; + + uint32_t hf_ind; + uint32_t ag_ind; + uint32_t ind_enable; + + /* AT command initialization indicator */ + uint8_t cmd_init_seq; + + /* The features supported by AT+CHLD */ + uint32_t chld_features; + + /* Worker for pending TX */ + struct k_work work; + + struct k_work_delayable deferred_work; + + /* calls */ + struct bt_hfp_hf_call calls[CONFIG_BT_HFP_HF_MAX_CALLS]; + + ATOMIC_DEFINE(flags, BT_HFP_HF_NUM_FLAGS); +}; diff --git a/subsys/bluetooth/host/classic/hfp_internal.h b/subsys/bluetooth/host/classic/hfp_internal.h index 116807187046e..03a85e1a1230f 100644 --- a/subsys/bluetooth/host/classic/hfp_internal.h +++ b/subsys/bluetooth/host/classic/hfp_internal.h @@ -41,37 +41,130 @@ #define BT_HFP_HF_FEATURE_ENH_VOICE_RECG 0x00000400 /* Enhanced Voice Recognition Status */ #define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT 0x00000800 /* Voice Recognition Text */ -/* HFP HF Supported features */ -#define BT_HFP_HF_SUPPORTED_FEATURES (BT_HFP_HF_FEATURE_CLI | BT_HFP_HF_FEATURE_VOLUME) +/* HFP HF Features in SDP */ +#define BT_HFP_HF_SDP_FEATURE_ECNR BIT(0) /* EC and/or NR function */ +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL BIT(1) /* Three-way calling */ +#define BT_HFP_HF_SDP_FEATURE_CLI BIT(2) /* CLI presentation */ +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG BIT(3) /* Voice recognition */ +#define BT_HFP_HF_SDP_FEATURE_VOLUME BIT(4) /* Remote volume control */ +#define BT_HFP_HF_SDP_FEATURE_WBS BIT(5) /* Wide Band Speech */ +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG BIT(6) /* Enhanced Voice Recognition Status */ +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ +#define BT_HFP_HF_SDP_FEATURE_SUPER_WBS BIT(7) /* Super Wide Band Speech */ +/* HFP AG Features in SDP */ +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL BIT(0) /* Three-way calling */ +#define BT_HFP_AG_SDP_FEATURE_ECNR BIT(1) /* EC and/or NR function */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG BIT(2) /* Voice recognition */ +#define BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE BIT(3) /* In-band ring tone capability */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG BIT(4) /* Attach no. to voice tag */ +#define BT_HFP_AG_SDP_FEATURE_WBS BIT(5) /* Wide Band Speech */ +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG BIT(6) /* Enhanced Voice Recognition Status */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ +#define BT_HFP_AG_SDP_FEATURE_SUPER_WBS BIT(8) /* Super Wide Band Speech */ #define HF_MAX_BUF_LEN BT_HF_CLIENT_MAX_PDU #define HF_MAX_AG_INDICATORS 20 -struct bt_hfp_hf { - struct bt_rfcomm_dlc rfcomm_dlc; - /* ACL connection handle */ - struct bt_conn *acl; - /* SCO Channel */ - struct bt_sco_chan chan; - char hf_buffer[HF_MAX_BUF_LEN]; - struct at_client at; - uint32_t hf_features; - uint32_t ag_features; - int8_t ind_table[HF_MAX_AG_INDICATORS]; -}; +#define BT_HFP_HF_VGM_GAIN_MAX 15 +#define BT_HFP_HF_VGM_GAIN_MIN 0 -enum hfp_hf_ag_indicators { - HF_SERVICE_IND, - HF_CALL_IND, - HF_CALL_SETUP_IND, - HF_CALL_HELD_IND, - HF_SINGNAL_IND, - HF_ROAM_IND, - HF_BATTERY_IND -}; +#define BT_HFP_HF_VGS_GAIN_MAX 15 +#define BT_HFP_HF_VGS_GAIN_MIN 0 /* HFP call setup status */ #define BT_HFP_CALL_SETUP_NONE 0 #define BT_HFP_CALL_SETUP_INCOMING 1 #define BT_HFP_CALL_SETUP_OUTGOING 2 #define BT_HFP_CALL_SETUP_REMOTE_ALERTING 3 + +/* HFP incoming call status */ +#define BT_HFP_BTRH_ON_HOLD 0 +#define BT_HFP_BTRH_ACCEPTED 1 +#define BT_HFP_BTRH_REJECTED 2 + +/* HFP Call held status */ +/* No calls held */ +#define BT_HFP_CALL_HELD_NONE 0 +/* Call is placed on hold or active/held calls swapped + * The AG has both an active AND a held call + */ +#define BT_HFP_CALL_HELD_ACTIVE_HELD 1 +/* Call on hold, no active call */ +#define BT_HFP_CALL_HELD_HELD 2 + +/* HFP AT+CHLD command value */ +/* Releases all held calls or sets User Determined User Busy + * (UDUB) for a waiting call + */ +#define BT_HFP_CHLD_RELEASE_ALL 0 +/* Releases all active calls (if any exist) and accepts the + * other (held or waiting) call. + */ +#define BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER 1 +/* Places all active calls (if any exist) on hold and accepts + * the other (held or waiting) call + */ +#define BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER 2 +/* Adds a held call to the conversation */ +#define BT_HFP_CALL_ACTIVE_HELD 3 +/* Connects the two calls and disconnects the subscriber from + * both calls (Explicit Call Transfer). + * Support for this value and its associated functionality is + * optional for the HF. + */ +#define BT_HFP_CALL_QUITE 4 +/* Release a specific active call */ +#define BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE 10 +/* Private Consultation Mode + * place all parties of a multiparty call on hold with the + * exception of the specified call. + */ +#define BT_HFP_CALL_PRIVATE_CNLTN_MODE 20 + +/* Active */ +#define BT_HFP_CLCC_STATUS_ACTIVE 0 +/* Held */ +#define BT_HFP_CLCC_STATUS_HELD 1 +/* Dialing (outgoing calls only) */ +#define BT_HFP_CLCC_STATUS_DIALING 2 +/* Alerting (outgoing calls only) */ +#define BT_HFP_CLCC_STATUS_ALERTING 3 +/* Incoming (incoming calls only) */ +#define BT_HFP_CLCC_STATUS_INCOMING 4 +/* Waiting (incoming calls only) */ +#define BT_HFP_CLCC_STATUS_WAITING 5 +/* Call held by Response and Hold */ +#define BT_HFP_CLCC_STATUS_CALL_HELD_HOLD 6 + +/* BVRA Value */ +/* BVRA Deactivation */ +#define BT_HFP_BVRA_DEACTIVATION 0 +/* BVRA Activation */ +#define BT_HFP_BVRA_ACTIVATION 1 +/* Ready to accept audio */ +#define BT_HFP_BVRA_READY_TO_ACCEPT 2 + +/* a maximum of 4 characters in length, but + * less than 4 characters in length is valid. + */ +#define BT_HFP_BVRA_TEXT_ID_MAX_LEN 4 + +/* BVRA VRE state */ +/* BVRA VRE state: the AG is ready to accept audio input */ +#define BT_HFP_BVRA_STATE_ACCEPT_INPUT BIT(0) +/* BVRA VRE state: the AG is sending audio to the HF */ +#define BT_HFP_BVRA_STATE_SEND_AUDIO BIT(1) +/* BVRA VRE state: the AG is processing the audio input */ +#define BT_HFP_BVRA_STATE_PROCESS_AUDIO BIT(2) + +#define IS_VALID_DTMF(c) ((((c) >= '0') && ((c) <= '9')) || \ + (((c) >= 'A') && ((c) <= 'D')) || ((c) == '#') || ((c) == '*')) + +/* HFP HF Indicators */ +enum { + HFP_HF_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ + HFP_HF_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */ + HFP_HF_IND_MAX +}; + +#define IS_VALID_BATTERY_LEVEL(level) (((level) >= 0) && ((level) <= 100)) diff --git a/subsys/bluetooth/host/classic/shell/CMakeLists.txt b/subsys/bluetooth/host/classic/shell/CMakeLists.txt index dbf42a44cb1ac..2732cb2eb6d94 100644 --- a/subsys/bluetooth/host/classic/shell/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/shell/CMakeLists.txt @@ -5,3 +5,8 @@ zephyr_library_sources(bredr.c) zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM rfcomm.c) zephyr_library_sources_ifdef(CONFIG_BT_A2DP a2dp.c) zephyr_library_sources_ifdef(CONFIG_BT_AVRCP avrcp.c) +if(CONFIG_BT_HFP_HF OR CONFIG_BT_HFP_AG) + zephyr_library_sources( + hfp.c + ) +endif() diff --git a/subsys/bluetooth/host/classic/shell/bredr.c b/subsys/bluetooth/host/classic/shell/bredr.c index 0c78355c2cb10..4bd635f2bb0b4 100644 --- a/subsys/bluetooth/host/classic/shell/bredr.c +++ b/subsys/bluetooth/host/classic/shell/bredr.c @@ -753,6 +753,58 @@ static uint8_t sdp_hfp_ag_user(struct bt_conn *conn, struct bt_sdp_client_result return BT_SDP_DISCOVER_UUID_CONTINUE; } +static uint8_t sdp_hfp_hf_user(struct bt_conn *conn, + struct bt_sdp_client_result *result, + const struct bt_sdp_discover_params *params) +{ + char addr[BT_ADDR_STR_LEN]; + uint16_t param, version; + uint16_t features; + int err; + + conn_addr_str(conn, addr, sizeof(addr)); + + if (result && result->resp_buf) { + bt_shell_print("SDP HFPHF data@%p (len %u) hint %u from remote %s", + result->resp_buf, result->resp_buf->len, result->next_record_hint, + addr); + + /* + * Focus to get BT_SDP_ATTR_PROTO_DESC_LIST attribute item to + * get HFPHF Server Channel Number operating on RFCOMM protocol. + */ + err = bt_sdp_get_proto_param(result->resp_buf, BT_SDP_PROTO_RFCOMM, ¶m); + if (err < 0) { + bt_shell_error("Error getting Server CN, err %d", err); + goto done; + } + bt_shell_print("HFPHF Server CN param 0x%04x", param); + + err = bt_sdp_get_profile_version(result->resp_buf, BT_SDP_HANDSFREE_SVCLASS, + &version); + if (err < 0) { + bt_shell_error("Error getting profile version, err %d", err); + goto done; + } + bt_shell_print("HFP version param 0x%04x", version); + + /* + * Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to + * get profile Supported Features mask. + */ + err = bt_sdp_get_features(result->resp_buf, &features); + if (err < 0) { + bt_shell_error("Error getting HFPHF Features, err %d", err); + goto done; + } + bt_shell_print("HFPHF Supported Features param 0x%04x", features); + } else { + bt_shell_print("No SDP HFPHF data from remote %s", addr); + } +done: + return BT_SDP_DISCOVER_UUID_CONTINUE; +} + static uint8_t sdp_a2src_user(struct bt_conn *conn, struct bt_sdp_client_result *result, const struct bt_sdp_discover_params *params) { @@ -816,6 +868,13 @@ static struct bt_sdp_discover_params discov_hfpag = { .pool = &sdp_client_pool, }; +static struct bt_sdp_discover_params discov_hfphf = { + .type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR, + .uuid = BT_UUID_DECLARE_16(BT_SDP_HANDSFREE_SVCLASS), + .func = sdp_hfp_hf_user, + .pool = &sdp_client_pool, +}; + static struct bt_sdp_discover_params discov_a2src = { .type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR, .uuid = BT_UUID_DECLARE_16(BT_SDP_AUDIO_SOURCE_SVCLASS), @@ -839,6 +898,8 @@ static int cmd_sdp_find_record(const struct shell *sh, size_t argc, char *argv[] if (!strcmp(action, "HFPAG")) { discov = discov_hfpag; + } else if (!strcmp(action, "HFPHF")) { + discov = discov_hfphf; } else if (!strcmp(action, "A2SRC")) { discov = discov_a2src; } else { @@ -908,7 +969,7 @@ SHELL_STATIC_SUBCMD_SET_CREATE(br_cmds, SHELL_CMD(l2cap, &l2cap_cmds, HELP_NONE, cmd_default_handler), SHELL_CMD_ARG(oob, NULL, NULL, cmd_oob, 1, 0), SHELL_CMD_ARG(pscan, NULL, "", cmd_connectable, 2, 0), - SHELL_CMD_ARG(sdp-find, NULL, "", cmd_sdp_find_record, 2, 0), + SHELL_CMD_ARG(sdp-find, NULL, "", cmd_sdp_find_record, 2, 0), SHELL_SUBCMD_SET_END ); diff --git a/subsys/bluetooth/host/classic/shell/hfp.c b/subsys/bluetooth/host/classic/shell/hfp.c new file mode 100644 index 0000000000000..7d713d81c34f7 --- /dev/null +++ b/subsys/bluetooth/host/classic/shell/hfp.c @@ -0,0 +1,1904 @@ +/* + * Copyright 2024-2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "host/shell/bt.h" +#include "common/bt_shell_private.h" + +#define HELP_NONE "[none]" + +extern struct bt_conn *default_conn; + +#if defined(CONFIG_BT_HFP_HF) + +struct bt_conn *hf_conn; +struct bt_hfp_hf *hfp_hf; +struct bt_conn *hf_sco_conn; +static struct bt_hfp_hf_call *hfp_hf_call[CONFIG_BT_HFP_HF_MAX_CALLS]; + +static void hf_add_a_call(struct bt_hfp_hf_call *call) +{ + for (size_t index = 0; index < ARRAY_SIZE(hfp_hf_call); index++) { + if (!hfp_hf_call[index]) { + hfp_hf_call[index] = call; + return; + } + } +} + +static void hf_remove_a_call(struct bt_hfp_hf_call *call) +{ + for (size_t index = 0; index < ARRAY_SIZE(hfp_hf_call); index++) { + if (call == hfp_hf_call[index]) { + hfp_hf_call[index] = NULL; + return; + } + } +} + +static void hf_connected(struct bt_conn *conn, struct bt_hfp_hf *hf) +{ + hf_conn = conn; + hfp_hf = hf; + bt_shell_print("HF connected"); +} + +static void hf_disconnected(struct bt_hfp_hf *hf) +{ + hf_conn = NULL; + hfp_hf = NULL; + bt_shell_print("HF disconnected"); +} + +static void hf_sco_connected(struct bt_hfp_hf *hf, struct bt_conn *sco_conn) +{ + bt_shell_print("HF SCO connected"); + hf_sco_conn = sco_conn; +} + +static void hf_sco_disconnected(struct bt_conn *sco_conn, uint8_t reason) +{ + bt_shell_print("HF SCO disconnected"); + hf_sco_conn = NULL; +} + +void hf_service(struct bt_hfp_hf *hf, uint32_t value) +{ + bt_shell_print("HF service %d", value); +} + +void hf_outgoing(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call) +{ + hf_add_a_call(call); + bt_shell_print("HF call %p outgoing", call); +} + +void hf_remote_ringing(struct bt_hfp_hf_call *call) +{ + bt_shell_print("HF remote call %p start ringing", call); +} + +void hf_incoming(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call) +{ + hf_add_a_call(call); + bt_shell_print("HF call %p incoming", call); +} + +void hf_incoming_held(struct bt_hfp_hf_call *call) +{ + bt_shell_print("HF call %p is held", call); +} + +void hf_accept(struct bt_hfp_hf_call *call) +{ + bt_shell_print("HF call %p accepted", call); +} + +void hf_reject(struct bt_hfp_hf_call *call) +{ + hf_remove_a_call(call); + bt_shell_print("HF call %p rejected", call); +} + +void hf_terminate(struct bt_hfp_hf_call *call) +{ + hf_remove_a_call(call); + bt_shell_print("HF call %p terminated", call); +} + +void hf_held(struct bt_hfp_hf_call *call) +{ + bt_shell_print("hf call %p held", call); +} + +void hf_retrieve(struct bt_hfp_hf_call *call) +{ + bt_shell_print("hf call %p retrieve", call); +} + +void hf_signal(struct bt_hfp_hf *hf, uint32_t value) +{ + bt_shell_print("HF signal %d", value); +} + +void hf_roam(struct bt_hfp_hf *hf, uint32_t value) +{ + bt_shell_print("HF roam %d", value); +} + +void hf_battery(struct bt_hfp_hf *hf, uint32_t value) +{ + bt_shell_print("HF battery %d", value); +} + +void hf_ring_indication(struct bt_hfp_hf_call *call) +{ + bt_shell_print("HF call %p ring", call); +} + +void hf_dialing(struct bt_hfp_hf *hf, int err) +{ + bt_shell_print("HF start dialing call: err %d", err); +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +void hf_clip(struct bt_hfp_hf_call *call, char *number, uint8_t type) +{ + bt_shell_print("HF call %p CLIP %s %d", call, number, type); +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static void hf_vgm(struct bt_hfp_hf *hf, uint8_t gain) +{ + bt_shell_print("HF VGM %d", gain); +} + +static void hf_vgs(struct bt_hfp_hf *hf, uint8_t gain) +{ + bt_shell_print("HF VGS %d", gain); +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +static void hf_inband_ring(struct bt_hfp_hf *hf, bool inband) +{ + bt_shell_print("HF ring: %s", inband ? "in-band" : "no in-hand"); +} + +static void hf_operator(struct bt_hfp_hf *hf, uint8_t mode, uint8_t format, char *operator) +{ + bt_shell_print("HF mode %d, format %d, operator %s", mode, format, operator); +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static void hf_codec_negotiate(struct bt_hfp_hf *hf, uint8_t id) +{ + bt_shell_print("codec negotiation: %d", id); +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_ECNR) +static void hf_ecnr_turn_off(struct bt_hfp_hf *hf, int err) +{ + bt_shell_print("Turn off ECNR: %d", err); +} +#endif /* CONFIG_BT_HFP_HF_ECNR */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static void hf_call_waiting(struct bt_hfp_hf_call *call, char *number, uint8_t type) +{ + bt_shell_print("3way call %p waiting. number %s type %d", call, number, type); +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +void hf_voice_recognition(struct bt_hfp_hf *hf, bool activate) +{ + bt_shell_print("Voice recognition %s", activate ? "activate" : "deactivate"); +} + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +void hf_vre_state(struct bt_hfp_hf *hf, uint8_t state) +{ + bt_shell_print("Voice recognition engine state %d", state); +} +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) +void hf_textual_representation(struct bt_hfp_hf *hf, char *id, uint8_t type, uint8_t operation, + char *text) +{ + bt_shell_print("Text id %s, type %d, operation %d, string %s", id, type, operation, text); +} +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +void hf_request_phone_number(struct bt_hfp_hf *hf, const char *number) +{ + if (number) { + bt_shell_print("Requested phone number %s", number); + } else { + bt_shell_print("Failed to request phone number"); + } +} + +void hf_subscriber_number(struct bt_hfp_hf *hf, const char *number, uint8_t type, uint8_t service) +{ + bt_shell_print("Subscriber number %s, type %d, service %d", number, type, service); +} + +static struct bt_hfp_hf_cb hf_cb = { + .connected = hf_connected, + .disconnected = hf_disconnected, + .sco_connected = hf_sco_connected, + .sco_disconnected = hf_sco_disconnected, + .service = hf_service, + .outgoing = hf_outgoing, + .remote_ringing = hf_remote_ringing, + .incoming = hf_incoming, + .incoming_held = hf_incoming_held, + .accept = hf_accept, + .reject = hf_reject, + .terminate = hf_terminate, + .held = hf_held, + .retrieve = hf_retrieve, + .signal = hf_signal, + .roam = hf_roam, + .battery = hf_battery, + .ring_indication = hf_ring_indication, + .dialing = hf_dialing, +#if defined(CONFIG_BT_HFP_HF_CLI) + .clip = hf_clip, +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + .vgm = hf_vgm, + .vgs = hf_vgs, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + .inband_ring = hf_inband_ring, + .operator = hf_operator, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + .codec_negotiate = hf_codec_negotiate, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +#if defined(CONFIG_BT_HFP_HF_ECNR) + .ecnr_turn_off = hf_ecnr_turn_off, +#endif /* CONFIG_BT_HFP_HF_ECNR */ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + .call_waiting = hf_call_waiting, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + .voice_recognition = hf_voice_recognition, +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + .vre_state = hf_vre_state, +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) + .textual_representation = hf_textual_representation, +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + .request_phone_number = hf_request_phone_number, + .subscriber_number = hf_subscriber_number, +}; + +static int cmd_reg_enable(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_register(&hf_cb); + if (err) { + shell_error(sh, "Callback register failed: %d", err); + } + + return err; +} + +static int cmd_connect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + struct bt_hfp_hf *hf; + uint8_t channel; + + channel = atoi(argv[1]); + + err = bt_hfp_hf_connect(default_conn, &hf, channel); + if (err) { + shell_error(sh, "Connect failed: %d", err); + } + + return err; +} + +static int cmd_disconnect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_disconnect(hfp_hf); + if (err) { + shell_error(sh, "Disconnect failed: %d", err); + } + + return err; +} + +static int cmd_sco_disconnect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_conn_disconnect(hf_sco_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) { + shell_error(sh, "Disconnect failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int cmd_cli_enable(const struct shell *sh, size_t argc, char **argv) +{ + const char *action = argv[1]; + bool enable; + int err; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_hf_cli(hfp_hf, enable); + if (err) { + shell_error(sh, "Fail to send AT+CLIP=%d (err %d)", enable, err); + return -ENOEXEC; + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int cmd_vgm_enable(const struct shell *sh, size_t argc, char **argv) +{ + uint32_t gain; + int err; + + gain = atoi(argv[1]); + + err = bt_hfp_hf_vgm(hfp_hf, gain); + if (err) { + shell_error(sh, "Fail to send AT+VGM=%d (err %d)", gain, err); + } + + return err; +} + +static int cmd_vgs_enable(const struct shell *sh, size_t argc, char **argv) +{ + uint32_t gain; + int err; + + gain = atoi(argv[1]); + + err = bt_hfp_hf_vgs(hfp_hf, gain); + if (err) { + shell_error(sh, "Fail to send AT+VGS=%d (err %d)", gain, err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +static int cmd_operator(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_get_operator(hfp_hf); + if (err) { + shell_error(sh, "Failed to read network operator: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int cmd_audio_connect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_audio_connect(hfp_hf); + if (err) { + shell_error(sh, "Failed to start audio connection procedure: %d", err); + } + + return err; +} + +static int cmd_select_codec(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t codec_id; + + codec_id = atoi(argv[1]); + + err = bt_hfp_hf_select_codec(hfp_hf, codec_id); + if (err) { + shell_error(sh, "Failed to select codec id: %d", err); + } + + return err; +} + +static int cmd_set_codecs(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t codec_ids; + + codec_ids = atoi(argv[1]); + + err = bt_hfp_hf_set_codecs(hfp_hf, codec_ids); + if (err) { + shell_error(sh, "Failed to set codecs: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +static int cmd_accept(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_accept(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to accept call: %d", err); + } + + return err; +} + +static int cmd_reject(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_reject(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to reject call: %d", err); + } + + return err; +} + +static int cmd_terminate(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_terminate(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to terminate call: %d", err); + } + + return err; +} + +static int cmd_hold_incoming(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_hold_incoming(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to put incoming call on hold: %d", err); + } + + return err; +} + +static int cmd_query_respond_hold_status(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_query_respond_hold_status(hfp_hf); + if (err) { + shell_error(sh, "Failed to query respond and hold status: %d", err); + } + + return err; +} + +static int cmd_number_call(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_number_call(hfp_hf, argv[1]); + if (err) { + shell_error(sh, "Failed to start phone number call: %d", err); + } + + return err; +} + +static int cmd_memory_dial(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_memory_dial(hfp_hf, argv[1]); + if (err) { + shell_error(sh, "Failed to memory dial call: %d", err); + } + + return err; +} + +static int cmd_redial(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_redial(hfp_hf); + if (err) { + shell_error(sh, "Failed to redial call: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_ECNR) +static int cmd_turn_off_ecnr(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_turn_off_ecnr(hfp_hf); + if (err) { + shell_error(sh, "Failed to turn off ecnr: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_ECNR */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int cmd_call_waiting_notify(const struct shell *sh, size_t argc, char **argv) +{ + const char *action = argv[1]; + bool enable; + int err; + + action = argv[1]; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_hf_call_waiting_notify(hfp_hf, enable); + if (err) { + shell_error(sh, "Failed to set call waiting notify: %d", err); + } + + return err; +} + +static int cmd_release_all_held(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_release_all_held(hfp_hf); + if (err) { + shell_error(sh, "Failed to release all held: %d", err); + } + + return err; +} + +static int cmd_set_udub(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_set_udub(hfp_hf); + if (err) { + shell_error(sh, "Failed to reject waiting call: %d", err); + } + + return err; +} + +static int cmd_release_active_accept_other(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_release_active_accept_other(hfp_hf); + if (err) { + shell_error(sh, "Failed to release active calls and accept other call: %d", err); + } + + return err; +} + +static int cmd_hold_active_accept_other(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_hold_active_accept_other(hfp_hf); + if (err) { + shell_error(sh, "Failed to hold all active calls and accept other call: %d", err); + } + + return err; +} + +static int cmd_join_conversation(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_join_conversation(hfp_hf); + if (err) { + shell_error(sh, "Failed to join the conversation: %d", err); + } + + return err; +} + +static int cmd_explicit_call_transfer(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_explicit_call_transfer(hfp_hf); + if (err) { + shell_error(sh, "Failed to explicit call transfer: %d", err); + } + + return err; +} + +static int cmd_release_specified_call(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_release_specified_call(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to release specified call: %d", err); + } + + return err; +} + +static int cmd_private_consultation_mode(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_private_consultation_mode(hfp_hf_call[index]); + if (err) { + shell_error(sh, "Failed to set private consultation mode: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +static int cmd_voice_recognition(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + bool activate; + int err; + + action = argv[1]; + + if (strcmp(action, "activate") == 0) { + activate = true; + } else if (strcmp(action, "deactivate") == 0) { + activate = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_hf_voice_recognition(hfp_hf, activate); + if (err) { + shell_error(sh, "Failed to set voice recognition: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +static int cmd_ready_to_accept_audio(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_ready_to_accept_audio(hfp_hf); + if (err) { + shell_error(sh, "Failed to send ready to accept audio notify: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +static int cmd_request_phone_number(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_request_phone_number(hfp_hf); + if (err) { + shell_error(sh, "Failed to request phone number: %d", err); + } + + return err; +} + +static int cmd_transmit_dtmf_code(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_hf_call))) || !hfp_hf_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_hf_transmit_dtmf_code(hfp_hf_call[index], argv[2][0]); + if (err) { + shell_error(sh, "Failed to transmit DTMF Code: %d", err); + } + + return err; +} + +static int cmd_query_subscriber(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_hf_query_subscriber(hfp_hf); + if (err) { + shell_error(sh, "Failed to query subscriber: %d", err); + } + + return err; +} + +static int cmd_indicator_status(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t status[4]; + size_t len; + + len = hex2bin(argv[1], strlen(argv[1]), status, sizeof(status)); + if (len == 0) { + shell_error(sh, "Failed to parse status %s", argv[1]); + return -EINVAL; + } + + err = bt_hfp_hf_indicator_status(hfp_hf, (uint32_t)status[0]); + if (err) { + shell_error(sh, "Failed to set AG indicator activated/deactivated status: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) +static int cmd_enhanced_safety(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + bool enable; + int err; + + action = argv[1]; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_hf_enhanced_safety(hfp_hf, enable); + if (err) { + shell_error(sh, "Failed to transfer enhanced safety status: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) +static int cmd_battery(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int level; + + level = atoi(argv[1]); + + err = bt_hfp_hf_battery(hfp_hf, level); + if (err) { + shell_error(sh, "Failed to transfer battery level: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ + +SHELL_STATIC_SUBCMD_SET_CREATE(hf_cmds, + SHELL_CMD_ARG(reg, NULL, HELP_NONE, cmd_reg_enable, 1, 0), + SHELL_CMD_ARG(connect, NULL, "", cmd_connect, 2, 0), + SHELL_CMD_ARG(disconnect, NULL, HELP_NONE, cmd_disconnect, 1, 0), + SHELL_CMD_ARG(sco_disconnect, NULL, HELP_NONE, cmd_sco_disconnect, 1, 0), +#if defined(CONFIG_BT_HFP_HF_CLI) + SHELL_CMD_ARG(cli, NULL, "", cmd_cli_enable, 2, 0), +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + SHELL_CMD_ARG(vgm, NULL, "", cmd_vgm_enable, 2, 0), + SHELL_CMD_ARG(vgs, NULL, "", cmd_vgs_enable, 2, 0), +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + SHELL_CMD_ARG(operator, NULL, HELP_NONE, cmd_operator, 1, 0), +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + SHELL_CMD_ARG(audio_connect, NULL, HELP_NONE, cmd_audio_connect, 1, 0), + SHELL_CMD_ARG(select_codec, NULL, "Codec ID", cmd_select_codec, 2, 0), + SHELL_CMD_ARG(set_codecs, NULL, "Codec ID Map", cmd_set_codecs, 2, 0), +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + SHELL_CMD_ARG(accept, NULL, "", cmd_accept, 2, 0), + SHELL_CMD_ARG(reject, NULL, "", cmd_reject, 2, 0), + SHELL_CMD_ARG(terminate, NULL, "", cmd_terminate, 2, 0), + SHELL_CMD_ARG(hold_incoming, NULL, "", cmd_hold_incoming, 2, 0), + SHELL_CMD_ARG(query_respond_hold_status, NULL, HELP_NONE, cmd_query_respond_hold_status, 1, + 0), + SHELL_CMD_ARG(number_call, NULL, "", cmd_number_call, 2, 0), + SHELL_CMD_ARG(memory_dial, NULL, "", cmd_memory_dial, 2, 0), + SHELL_CMD_ARG(redial, NULL, HELP_NONE, cmd_redial, 1, 0), +#if defined(CONFIG_BT_HFP_HF_ECNR) + SHELL_CMD_ARG(turn_off_ecnr, NULL, HELP_NONE, cmd_turn_off_ecnr, 1, 0), +#endif /* CONFIG_BT_HFP_HF_ECNR */ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + SHELL_CMD_ARG(call_waiting_notify, NULL, "", cmd_call_waiting_notify, 2, 0), + SHELL_CMD_ARG(release_all_held, NULL, HELP_NONE, cmd_release_all_held, 1, 0), + SHELL_CMD_ARG(set_udub, NULL, HELP_NONE, cmd_set_udub, 1, 0), + SHELL_CMD_ARG(release_active_accept_other, NULL, HELP_NONE, cmd_release_active_accept_other, + 1, 0), + SHELL_CMD_ARG(hold_active_accept_other, NULL, HELP_NONE, cmd_hold_active_accept_other, 1, + 0), + SHELL_CMD_ARG(join_conversation, NULL, HELP_NONE, cmd_join_conversation, 1, 0), + SHELL_CMD_ARG(explicit_call_transfer, NULL, HELP_NONE, cmd_explicit_call_transfer, 1, 0), + SHELL_CMD_ARG(release_specified_call, NULL, "", cmd_release_specified_call, 2, + 0), + SHELL_CMD_ARG(private_consultation_mode, NULL, "", + cmd_private_consultation_mode, 2, 0), +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + SHELL_CMD_ARG(voice_recognition, NULL, "", cmd_voice_recognition, 2, + 0), +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + SHELL_CMD_ARG(ready_to_accept_audio, NULL, HELP_NONE, cmd_ready_to_accept_audio, 1, 0), +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + SHELL_CMD_ARG(request_phone_number, NULL, HELP_NONE, cmd_request_phone_number, 1, 0), + SHELL_CMD_ARG(transmit_dtmf_code, NULL, " ", + cmd_transmit_dtmf_code, 3, 0), + SHELL_CMD_ARG(query_subscriber, NULL, HELP_NONE, cmd_query_subscriber, 1, 0), + SHELL_CMD_ARG(indicator_status, NULL, "", + cmd_indicator_status, 2, 0), +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) + SHELL_CMD_ARG(enhanced_safety, NULL, "", cmd_enhanced_safety, 2, 0), +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) + SHELL_CMD_ARG(battery, NULL, "", cmd_battery, 2, 0), +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ + SHELL_SUBCMD_SET_END +); +#endif /* CONFIG_BT_HFP_HF */ + +#if defined(CONFIG_BT_HFP_AG) + +struct bt_hfp_ag *hfp_ag; +struct bt_conn *hfp_ag_sco_conn; +static struct bt_hfp_ag_call *hfp_ag_call[CONFIG_BT_HFP_AG_MAX_CALLS]; + +static void ag_add_a_call(struct bt_hfp_ag_call *call) +{ + for (size_t index = 0; index < ARRAY_SIZE(hfp_ag_call); index++) { + if (!hfp_ag_call[index]) { + hfp_ag_call[index] = call; + return; + } + } +} + +static void ag_remove_a_call(struct bt_hfp_ag_call *call) +{ + for (size_t index = 0; index < ARRAY_SIZE(hfp_ag_call); index++) { + if (call == hfp_ag_call[index]) { + hfp_ag_call[index] = NULL; + return; + } + } +} + +static void ag_connected(struct bt_conn *conn, struct bt_hfp_ag *ag) +{ + if (conn != default_conn) { + bt_shell_warn("The conn %p is not aligned with ACL conn %p", conn, default_conn); + } + hfp_ag = ag; + bt_shell_print("ag connected"); +} + +static void ag_disconnected(struct bt_hfp_ag *ag) +{ + bt_shell_print("ag disconnected"); +} + +static void ag_sco_connected(struct bt_hfp_ag *ag, struct bt_conn *sco_conn) +{ + bt_shell_print("ag sco connected"); + hfp_ag_sco_conn = sco_conn; +} + +static void ag_sco_disconnected(struct bt_hfp_ag *ag) +{ + bt_shell_print("ag sco disconnected"); + hfp_ag_sco_conn = NULL; +} + +static int ag_memory_dial(struct bt_hfp_ag *ag, const char *location, char **number) +{ + static char *phone = "123456789"; + + if (strcmp(location, "0")) { + return -ENOTSUP; + } + + bt_shell_print("ag memory dial"); + + *number = phone; + + return 0; +} + +static int ag_number_call(struct bt_hfp_ag *ag, const char *number) +{ + static char *phone = "123456789"; + + bt_shell_print("ag number call"); + + if (strcmp(number, phone)) { + return -ENOTSUP; + } + + return 0; +} + +static void ag_outgoing(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number) +{ + bt_shell_print("ag outgoing call %p, number %s", call, number); + ag_add_a_call(call); +} + +static void ag_incoming(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, const char *number) +{ + bt_shell_print("ag incoming call %p, number %s", call, number); + ag_add_a_call(call); +} + +static void ag_incoming_held(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag incoming call %p is held", call); +} + +static void ag_ringing(struct bt_hfp_ag_call *call, bool in_band) +{ + bt_shell_print("ag call %p start ringing mode %d", call, in_band); +} + +static void ag_accept(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p accept", call); +} + +static void ag_held(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p held", call); +} + +static void ag_retrieve(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p retrieved", call); +} + +static void ag_reject(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p reject", call); + ag_remove_a_call(call); +} + +static void ag_terminate(struct bt_hfp_ag_call *call) +{ + bt_shell_print("ag call %p terminate", call); + ag_remove_a_call(call); +} + +static void ag_codec(struct bt_hfp_ag *ag, uint32_t ids) +{ + bt_shell_print("ag received codec id bit map %x", ids); +} + +void ag_vgm(struct bt_hfp_ag *ag, uint8_t gain) +{ + bt_shell_print("ag received vgm %d", gain); +} + +void ag_vgs(struct bt_hfp_ag *ag, uint8_t gain) +{ + bt_shell_print("ag received vgs %d", gain); +} + +void ag_codec_negotiate(struct bt_hfp_ag *ag, int err) +{ + bt_shell_print("ag codec negotiation result %d", err); +} + +void ag_audio_connect_req(struct bt_hfp_ag *ag) +{ + bt_shell_print("Receive audio connect request. " + "Input `hfp ag audio_connect` to start audio connect"); +} + +void ag_ecnr_turn_off(struct bt_hfp_ag *ag) +{ + bt_shell_print("encr is disabled"); +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +void ag_explicit_call_transfer(struct bt_hfp_ag *ag) +{ + bt_shell_print("explicit call transfer"); +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) +void ag_voice_recognition(struct bt_hfp_ag *ag, bool activate) +{ + bt_shell_print("AG Voice recognition %s", activate ? "activate" : "deactivate"); +} + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) +void ag_ready_to_accept_audio(struct bt_hfp_ag *ag) +{ + bt_shell_print("hf is ready to accept audio"); +} +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) +int ag_request_phone_number(struct bt_hfp_ag *ag, char **number) +{ + static bool valid_number; + + if (valid_number && number) { + valid_number = false; + *number = "123456789"; + return 0; + } + + valid_number = true; + return -EINVAL; +} +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + +void ag_transmit_dtmf_code(struct bt_hfp_ag *ag, char code) +{ + bt_shell_print("DTMF code is %c", code); +} + +struct { + char *number; + uint8_t type; + uint8_t service; +} ag_subscriber_number_info[] = { + { + .number = "12345678", + .type = 128, + .service = 4, + }, + { + .number = "87654321", + .type = 128, + .service = 4, + }, +}; + +static bool subscriber; + +int ag_subscriber_number(struct bt_hfp_ag *ag, bt_hfp_ag_query_subscriber_func_t func) +{ + int err; + + if (subscriber && func) { + for (size_t index = 0; index < ARRAY_SIZE(ag_subscriber_number_info); index++) { + err = func(ag, ag_subscriber_number_info[index].number, + ag_subscriber_number_info[index].type, + ag_subscriber_number_info[index].service); + if (err < 0) { + break; + } + } + } + return 0; +} + +void ag_hf_indicator_value(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, + uint32_t value) +{ + bt_shell_print("indicator %d value %d", indicator, value); +} + +static struct bt_hfp_ag_cb ag_cb = { + .connected = ag_connected, + .disconnected = ag_disconnected, + .sco_connected = ag_sco_connected, + .sco_disconnected = ag_sco_disconnected, + .memory_dial = ag_memory_dial, + .number_call = ag_number_call, + .outgoing = ag_outgoing, + .incoming = ag_incoming, + .incoming_held = ag_incoming_held, + .ringing = ag_ringing, + .accept = ag_accept, + .held = ag_held, + .retrieve = ag_retrieve, + .reject = ag_reject, + .terminate = ag_terminate, + .codec = ag_codec, + .codec_negotiate = ag_codec_negotiate, + .audio_connect_req = ag_audio_connect_req, + .vgm = ag_vgm, + .vgs = ag_vgs, +#if defined(CONFIG_BT_HFP_AG_ECNR) + .ecnr_turn_off = ag_ecnr_turn_off, +#endif /* CONFIG_BT_HFP_AG_ECNR */ +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + .explicit_call_transfer = ag_explicit_call_transfer, +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + .voice_recognition = ag_voice_recognition, +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + .ready_to_accept_audio = ag_ready_to_accept_audio, +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) + .request_phone_number = ag_request_phone_number, +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + .transmit_dtmf_code = ag_transmit_dtmf_code, + .subscriber_number = ag_subscriber_number, + .hf_indicator_value = ag_hf_indicator_value, +}; + +static int cmd_ag_reg_enable(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_register(&ag_cb); + if (err) { + shell_error(sh, "Callback register failed: %d", err); + } + + return err; +} + +static int cmd_ag_connect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + struct bt_hfp_ag *ag; + uint8_t channel; + + channel = atoi(argv[1]); + + err = bt_hfp_ag_connect(default_conn, &ag, channel); + if (err) { + shell_error(sh, "Connect failed: %d", err); + } + + return err; +} + +static int cmd_ag_disconnect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_disconnect(hfp_ag); + if (err) { + shell_error(sh, "Disconnect failed: %d", err); + } + + return err; +} + +static int cmd_ag_sco_disconnect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_conn_disconnect(hfp_ag_sco_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) { + shell_error(sh, "Disconnect failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_incoming(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_remote_incoming(hfp_ag, argv[1]); + if (err) { + shell_error(sh, "Set remote incoming failed: %d", err); + } + + return err; +} + +static int cmd_ag_hold_incoming(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_hold_incoming(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote incoming failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_reject(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_remote_reject(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote reject failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_accept(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_remote_accept(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote accept failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_terminate(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_remote_terminate(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote terminate failed: %d", err); + } + + return err; +} + +static int cmd_ag_remote_ringing(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_remote_ringing(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set remote ringing failed: %d", err); + } + + return err; +} + +static int cmd_ag_outgoing(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_outgoing(hfp_ag, argv[1]); + if (err) { + shell_error(sh, "Set outgoing failed: %d", err); + } + + return err; +} + +static int cmd_ag_reject(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_reject(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set reject failed: %d", err); + } + + return err; +} + +static int cmd_ag_accept(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_accept(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set accept failed: %d", err); + } + + return err; +} + +static int cmd_ag_hold(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_hold(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set hold failed: %d", err); + } + + return err; +} + +static int cmd_ag_retrieve(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_retrieve(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set retrieve failed: %d", err); + } + + return err; +} + +static int cmd_ag_terminate(const struct shell *sh, size_t argc, char **argv) +{ + int err; + int index; + + index = atoi(argv[1]); + if ((index >= ((int)ARRAY_SIZE(hfp_ag_call))) || !hfp_ag_call[index]) { + shell_error(sh, "Invalid call index: %d", index); + return -EINVAL; + } + + err = bt_hfp_ag_terminate(hfp_ag_call[index]); + if (err) { + shell_error(sh, "Set terminate failed: %d", err); + } + + return err; +} + +static int cmd_ag_vgm(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t vgm; + + vgm = atoi(argv[1]); + + err = bt_hfp_ag_vgm(hfp_ag, vgm); + if (err) { + shell_error(sh, "Set microphone gain failed: %d", err); + } + + return err; +} + +static int cmd_ag_vgs(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t vgs; + + vgs = atoi(argv[1]); + + err = bt_hfp_ag_vgs(hfp_ag, vgs); + if (err) { + shell_error(sh, "Set speaker gain failed: %d", err); + } + + return err; +} + +static int cmd_ag_operator(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t mode; + + mode = atoi(argv[1]); + + err = bt_hfp_ag_set_operator(hfp_ag, mode, argv[2]); + if (err) { + shell_error(sh, "Set network operator failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_AG_CODEC_NEG) +static int cmd_ag_audio_connect(const struct shell *sh, size_t argc, char **argv) +{ + int err; + uint8_t id; + + id = atoi(argv[1]); + + err = bt_hfp_ag_audio_connect(hfp_ag, id); + if (err) { + shell_error(sh, "Start audio connection procedure failed: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + +static int cmd_ag_inband_ringtone(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + bool enable; + int err; + + action = argv[1]; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_ag_inband_ringtone(hfp_ag, (bool)enable); + if (err) { + shell_error(sh, "Set inband ringtone failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static int cmd_ag_explicit_call_transfer(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + err = bt_hfp_ag_explicit_call_transfer(hfp_ag); + if (err) { + shell_error(sh, "Explicit call transfer failed: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) +static int cmd_ag_voice_recognition(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + bool enable; + int err; + + action = argv[1]; + + if (strcmp(action, "activate") == 0) { + enable = true; + } else if (strcmp(action, "deactivate") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_ag_voice_recognition(hfp_ag, enable); + if (err) { + shell_error(sh, "Set voice recognition failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) +static int cmd_ag_vre_state(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + uint8_t state = 0; + int err; + + action = argv[1]; + + for (size_t index = 0; index < strlen(action); index++) { + switch (action[index]) { + case 'R': + state |= BIT(0); + break; + case 'S': + state |= BIT(1); + break; + case 'P': + state |= BIT(2); + break; + } + } + + err = bt_hfp_ag_vre_state(hfp_ag, state); + if (err) { + shell_error(sh, "Set voice recognition engine state failed: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) +static int cmd_ag_vre_text(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + uint8_t state = 0; + const char *id; + uint8_t type; + uint8_t operation; + const char *text; + int err; + + action = argv[1]; + id = argv[2]; + type = (uint8_t)atoi(argv[3]); + operation = (uint8_t)atoi(argv[4]); + text = argv[5]; + + for (size_t index = 0; index < strlen(action); index++) { + switch (action[index]) { + case 'R': + state |= BIT(0); + break; + case 'S': + state |= BIT(1); + break; + case 'P': + state |= BIT(2); + break; + } + } + + err = bt_hfp_ag_vre_textual_representation(hfp_ag, state, id, type, operation, text); + if (err) { + shell_error(sh, "Set voice recognition engine textual representation failed: %d", + err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + +static int cmd_ag_subscriber(const struct shell *sh, size_t argc, char **argv) +{ + const char *action; + + action = argv[1]; + + if (strcmp(action, "empty") == 0) { + subscriber = false; + } else if (strcmp(action, "notempty") == 0) { + subscriber = true; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + return 0; +} + +static int cmd_ag_signal_strength(const struct shell *sh, size_t argc, char **argv) +{ + uint8_t strength; + int err; + + strength = (uint8_t)atoi(argv[1]); + + err = bt_hfp_ag_signal_strength(hfp_ag, strength); + if (err) { + shell_error(sh, "Set signal strength failed: %d", err); + } + + return err; +} + +static int cmd_ag_roaming_status(const struct shell *sh, size_t argc, char **argv) +{ + uint8_t status; + int err; + + status = (uint8_t)atoi(argv[1]); + + err = bt_hfp_ag_roaming_status(hfp_ag, status); + if (err) { + shell_error(sh, "Set roaming status failed: %d", err); + } + + return err; +} + +static int cmd_ag_battery_level(const struct shell *sh, size_t argc, char **argv) +{ + uint8_t level; + int err; + + level = (uint8_t)atoi(argv[1]); + + err = bt_hfp_ag_battery_level(hfp_ag, level); + if (err) { + shell_error(sh, "Set battery level failed: %d", err); + } + + return err; +} + +static int cmd_ag_service_availability(const struct shell *sh, size_t argc, char **argv) +{ + bool available; + const char *action; + int err; + + action = argv[1]; + + if (strcmp(action, "yes") == 0) { + available = true; + } else if (strcmp(action, "no") == 0) { + available = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_ag_service_availability(hfp_ag, available); + if (err) { + shell_error(sh, "Set service availability failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) +static int cmd_ag_hf_indicator(const struct shell *sh, size_t argc, char **argv) +{ + bool enable; + const char *action; + int err; + size_t indicator; + + indicator = atoi(argv[1]); + action = argv[2]; + + if (strcmp(action, "enable") == 0) { + enable = true; + } else if (strcmp(action, "disable") == 0) { + enable = false; + } else { + shell_error(sh, "Invalid option."); + return -ENOEXEC; + } + + err = bt_hfp_ag_hf_indicator(hfp_ag, indicator, enable); + if (err) { + shell_error(sh, "Activate/deactivate HF indicator failed: %d", err); + } + + return err; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +#define HELP_AG_TEXTUAL_REPRESENTATION \ + "<[R-ready][S-send][P-processing]> " \ + " " + +SHELL_STATIC_SUBCMD_SET_CREATE(ag_cmds, + SHELL_CMD_ARG(reg, NULL, HELP_NONE, cmd_ag_reg_enable, 1, 0), + SHELL_CMD_ARG(connect, NULL, "", cmd_ag_connect, 2, 0), + SHELL_CMD_ARG(disconnect, NULL, HELP_NONE, cmd_ag_disconnect, 1, 0), + SHELL_CMD_ARG(sco_disconnect, NULL, HELP_NONE, cmd_ag_sco_disconnect, 1, 0), + SHELL_CMD_ARG(remote_incoming, NULL, "", cmd_ag_remote_incoming, 2, 0), + SHELL_CMD_ARG(hold_incoming, NULL, "", cmd_ag_hold_incoming, 2, 0), + SHELL_CMD_ARG(remote_reject, NULL, "", cmd_ag_remote_reject, 2, 0), + SHELL_CMD_ARG(remote_accept, NULL, "", cmd_ag_remote_accept, 2, 0), + SHELL_CMD_ARG(remote_terminate, NULL, "", cmd_ag_remote_terminate, 2, 0), + SHELL_CMD_ARG(remote_ringing, NULL, "", cmd_ag_remote_ringing, 2, 0), + SHELL_CMD_ARG(outgoing, NULL, "", cmd_ag_outgoing, 2, 0), + SHELL_CMD_ARG(reject, NULL, "", cmd_ag_reject, 2, 0), + SHELL_CMD_ARG(accept, NULL, "", cmd_ag_accept, 2, 0), + SHELL_CMD_ARG(hold, NULL, "", cmd_ag_hold, 2, 0), + SHELL_CMD_ARG(retrieve, NULL, "", cmd_ag_retrieve, 2, 0), + SHELL_CMD_ARG(terminate, NULL, "", cmd_ag_terminate, 2, 0), + SHELL_CMD_ARG(vgm, NULL, "", cmd_ag_vgm, 2, 0), + SHELL_CMD_ARG(vgs, NULL, "", cmd_ag_vgs, 2, 0), + SHELL_CMD_ARG(operator, NULL, " ", cmd_ag_operator, 3, 0), +#if defined(CONFIG_BT_HFP_AG_CODEC_NEG) + SHELL_CMD_ARG(audio_connect, NULL, "", cmd_ag_audio_connect, 2, 0), +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + SHELL_CMD_ARG(inband_ringtone, NULL, "", cmd_ag_inband_ringtone, 2, 0), +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + SHELL_CMD_ARG(explicit_call_transfer, NULL, HELP_NONE, cmd_ag_explicit_call_transfer, 1, 0), +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + SHELL_CMD_ARG(voice_recognition, NULL, "", cmd_ag_voice_recognition, 2, + 0), +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + SHELL_CMD_ARG(vre_state, NULL, "<[R-ready][S-send][P-processing]>", cmd_ag_vre_state, 2, 0), +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) + SHELL_CMD_ARG(vre_text, NULL, HELP_AG_TEXTUAL_REPRESENTATION, cmd_ag_vre_text, 6, 0), +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + SHELL_CMD_ARG(subscriber, NULL, "", cmd_ag_subscriber, 2, 0), + SHELL_CMD_ARG(signal_strength, NULL, "", cmd_ag_signal_strength, 2, 0), + SHELL_CMD_ARG(roaming_status, NULL, "", cmd_ag_roaming_status, 2, 0), + SHELL_CMD_ARG(battery_level, NULL, "", cmd_ag_battery_level, 2, 0), + SHELL_CMD_ARG(service_availability, NULL, "", cmd_ag_service_availability, 2, 0), +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) + SHELL_CMD_ARG(hf_indicator, NULL, " ", cmd_ag_hf_indicator, 3, + 0), +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + SHELL_SUBCMD_SET_END +); +#endif /* CONFIG_BT_HFP_AG */ + +static int cmd_default(const struct shell *sh, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help(sh); + /* sh returns 1 when help is printed */ + return SHELL_CMD_HELP_PRINTED; + } + + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + + return -ENOEXEC; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(hfp_cmds, +#if defined(CONFIG_BT_HFP_HF) + SHELL_CMD(hf, &hf_cmds, "HFP HF shell commands", cmd_default), +#endif /* CONFIG_BT_HFP_HF */ +#if defined(CONFIG_BT_HFP_AG) + SHELL_CMD(ag, &ag_cmds, "HFP AG shell commands", cmd_default), +#endif /* CONFIG_BT_HFP_AG */ + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_ARG_REGISTER(hfp, &hfp_cmds, "Bluetooth HFP shell commands", cmd_default, 1, 1); diff --git a/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.conf b/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.conf new file mode 100644 index 0000000000000..c214409ea689b --- /dev/null +++ b/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.conf @@ -0,0 +1,30 @@ +#select NXP NW612 Chipset +CONFIG_BT_NXP_NW612=y +#Enable entropy generator +CONFIG_ENTROPY_GENERATOR=y + +CONFIG_BT_HFP_HF_CODEC_NEG=y +CONFIG_BT_HFP_AG_CODEC_NEG=y + +CONFIG_BT_HFP_HF_ECNR=y +CONFIG_BT_HFP_AG_ECNR=y + +CONFIG_BT_HFP_HF_3WAY_CALL=y +CONFIG_BT_HFP_AG_3WAY_CALL=y + +CONFIG_BT_HFP_HF_VOICE_RECG_TEXT=y +CONFIG_BT_HFP_AG_VOICE_RECG_TEXT=y + +CONFIG_BT_HFP_AG_VOICE_TAG=y + +CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY=y +CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY=y + +CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY=y +CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY=y + +CONFIG_BT_SETTINGS=n +CONFIG_FLASH=n +CONFIG_FLASH_MAP=n +CONFIG_NVS=n +CONFIG_SETTINGS=n diff --git a/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.overlay b/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.overlay new file mode 100644 index 0000000000000..0198087c363d5 --- /dev/null +++ b/tests/bluetooth/shell/boards/mimxrt1170_evk_mimxrt1176_cm7_B.overlay @@ -0,0 +1,12 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + chosen { + zephyr,sram = &dtcm; + zephyr,shell-uart = &lpuart1; + }; +}; diff --git a/tests/bluetooth/shell/prj_br.conf b/tests/bluetooth/shell/prj_br.conf index 12f51033f45aa..427fc3ec158d7 100644 --- a/tests/bluetooth/shell/prj_br.conf +++ b/tests/bluetooth/shell/prj_br.conf @@ -24,3 +24,6 @@ CONFIG_BT_L2CAP_STREAM=y CONFIG_BT_L2CAP_FCS=y CONFIG_BT_L2CAP_EXT_WIN_SIZE=y CONFIG_BT_L2CAP_MAX_WINDOW_SIZE=5 + +CONFIG_BT_HFP_HF=y +CONFIG_BT_HFP_AG=y