zephyr/subsys/bluetooth/host/classic/hfp_hf.c
Lyle Zhu bbc0f1fa57 Bluetooth: HFP_HF: Code clean
Remove useless function `bt_hfp_hf_send_cmd`.
And related callback `cmd_complete_cb`.

Signed-off-by: Lyle Zhu <lyle.zhu@nxp.com>
2025-04-03 06:24:32 +02:00

2127 lines
44 KiB
C

/* hfp_hf.c - Hands free Profile - Handsfree side handling */
/*
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <errno.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <zephyr/bluetooth/conn.h>
#include "common/assert.h"
#include <zephyr/bluetooth/classic/rfcomm.h>
#include <zephyr/bluetooth/classic/hfp_hf.h>
#include <zephyr/bluetooth/classic/sdp.h>
#include "host/hci_core.h"
#include "host/conn_internal.h"
#include "l2cap_br_internal.h"
#include "rfcomm_internal.h"
#include "at.h"
#include "sco_internal.h"
#include "hfp_internal.h"
#define LOG_LEVEL CONFIG_BT_HFP_HF_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_hfp_hf);
#define MAX_IND_STR_LEN 17
struct bt_hfp_hf_cb *bt_hf;
NET_BUF_POOL_FIXED_DEFINE(hf_pool, CONFIG_BT_MAX_CONN + 1,
BT_RFCOMM_BUF_SIZE(BT_HF_CLIENT_MAX_PDU),
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
static struct bt_hfp_hf bt_hfp_hf_pool[CONFIG_BT_MAX_CONN];
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;
uint32_t min;
uint32_t max;
} ag_ind[] = {
{"service", 0, 1}, /* HF_SERVICE_IND */
{"call", 0, 1}, /* HF_CALL_IND */
{"callsetup", 0, 3}, /* HF_CALL_SETUP_IND */
{"callheld", 0, 2}, /* HF_CALL_HELD_IND */
{"signal", 0, 5}, /* HF_SIGNAL_IND */
{"roam", 0, 1}, /* HF_ROAM_IND */
{"battchg", 0, 5} /* HF_BATTERY_IND */
};
/* HFP Hands-Free SDP record */
static struct bt_sdp_attribute hfp_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_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_HF)
},
)
},
)
),
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)
},
)
),
/* 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_SDP_SUPPORTED_FEATURES),
};
static struct bt_sdp_record hfp_rec = BT_SDP_RECORD(hfp_attrs);
void hf_slc_error(struct at_client *hf_at)
{
struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at);
int err;
LOG_ERR("SLC error: disconnecting");
err = bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc);
if (err) {
LOG_ERR("Rfcomm: Unable to disconnect :%d", -err);
}
}
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("Remove completed buf %p on %p", k_fifo_peek_head(&hf->tx_pending), hf);
(void)k_fifo_get(&hf->tx_pending, K_NO_WAIT);
} 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_TX_ONGOING)) {
return;
}
buf = k_fifo_peek_head(&hf->tx_pending);
if (!buf) {
return;
}
atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING);
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);
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, ...)
{
struct net_buf *buf;
va_list vargs;
int ret;
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) {
LOG_ERR("Unable to format variable arguments");
return ret;
}
va_end(vargs);
net_buf_add(buf, ret);
net_buf_add_u8(buf, '\r');
LOG_DBG("HF %p, DLC %p sending buf %p", hf, &hf->rfcomm_dlc, buf);
k_fifo_put(&hf->tx_pending, buf);
hfp_hf_send_data(hf);
return 0;
}
int brsf_handle(struct at_client *hf_at)
{
struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at);
uint32_t val;
int ret;
ret = at_get_number(hf_at, &val);
if (ret < 0) {
LOG_ERR("Error getting value");
return ret;
}
hf->ag_features = val;
return 0;
}
int brsf_resp(struct at_client *hf_at, struct net_buf *buf)
{
int err;
LOG_DBG("");
err = at_parse_cmd_input(hf_at, buf, "BRSF", brsf_handle,
AT_CMD_TYPE_NORMAL);
if (err < 0) {
/* Returning negative value is avoided before SLC connection
* established.
*/
LOG_ERR("Error parsing CMD input");
hf_slc_error(hf_at);
}
return 0;
}
static void cind_handle_values(struct at_client *hf_at, uint32_t index,
char *name, uint32_t min, uint32_t max)
{
struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at);
int i;
LOG_DBG("index: %u, name: %s, min: %u, max:%u", index, name, min, max);
for (i = 0; i < ARRAY_SIZE(ag_ind); i++) {
if (strcmp(name, ag_ind[i].name) != 0) {
continue;
}
if (min != ag_ind[i].min || max != ag_ind[i].max) {
LOG_ERR("%s indicator min/max value not matching", name);
}
hf->ind_table[index] = i;
break;
}
}
int cind_handle(struct at_client *hf_at)
{
uint32_t index = 0U;
/* Parsing Example: CIND: ("call",(0,1)) etc.. */
while (at_has_next_list(hf_at)) {
char name[MAX_IND_STR_LEN];
uint32_t min, max;
if (at_open_list(hf_at) < 0) {
LOG_ERR("Could not get open list");
goto error;
}
if (at_list_get_string(hf_at, name, sizeof(name)) < 0) {
LOG_ERR("Could not get string");
goto error;
}
if (at_open_list(hf_at) < 0) {
LOG_ERR("Could not get open list");
goto error;
}
if (at_list_get_range(hf_at, &min, &max) < 0) {
LOG_ERR("Could not get range");
goto error;
}
if (at_close_list(hf_at) < 0) {
LOG_ERR("Could not get close list");
goto error;
}
if (at_close_list(hf_at) < 0) {
LOG_ERR("Could not get close list");
goto error;
}
cind_handle_values(hf_at, index, name, min, max);
index++;
}
return 0;
error:
LOG_ERR("Error on CIND response");
hf_slc_error(hf_at);
return -EINVAL;
}
int cind_resp(struct at_client *hf_at, struct net_buf *buf)
{
int err;
err = at_parse_cmd_input(hf_at, buf, "CIND", cind_handle,
AT_CMD_TYPE_NORMAL);
if (err < 0) {
LOG_ERR("Error parsing CMD input");
hf_slc_error(hf_at);
}
return 0;
}
static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value)
{
struct bt_conn *conn = hf->acl;
atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_ACTIVE, value);
if (value) {
if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) {
if (bt_hf->incoming_held) {
bt_hf->incoming_held(conn);
}
} else {
if (bt_hf->accept) {
bt_hf->accept(conn);
}
}
} else {
if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) {
if (bt_hf->reject) {
bt_hf->reject(conn);
}
} else {
if (bt_hf->terminate) {
bt_hf->terminate(conn);
}
}
}
}
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);
struct bt_conn *conn = hf->acl;
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(conn, value);
}
break;
case HF_CALL_IND:
ag_indicator_handle_call(hf, value);
break;
case HF_CALL_SETUP_IND:
atomic_set_bit_to(hf->flags, BT_HFP_HF_FLAG_INCOMING,
value == BT_HFP_CALL_SETUP_INCOMING);
if (value == BT_HFP_CALL_SETUP_INCOMING) {
if (bt_hf->incoming) {
bt_hf->incoming(conn);
}
} else if (value == BT_HFP_CALL_SETUP_OUTGOING) {
if (bt_hf->outgoing) {
bt_hf->outgoing(conn);
}
} else if (value == BT_HFP_CALL_SETUP_REMOTE_ALERTING) {
if (bt_hf->remote_ringing) {
bt_hf->remote_ringing(conn);
}
}
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);
}
break;
case HF_BATTERY_IND:
if (bt_hf->battery) {
bt_hf->battery(conn, 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_conn *conn = hf->acl;
if (bt_hf->ring_indication) {
bt_hf->ring_indication(conn);
}
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);
struct bt_conn *conn = hf->acl;
char *number;
uint32_t type;
int err;
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;
}
if (bt_hf->clip) {
bt_hf->clip(conn, 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);
struct bt_conn *conn = hf->acl;
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(conn, (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);
struct bt_conn *conn = hf->acl;
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(conn, (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);
struct bt_conn *conn = hf->acl;
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(conn, (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);
struct bt_conn *conn = hf->acl;
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(conn, 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(conn, codec_id);
return 0;
}
err = bt_hfp_hf_select_codec(conn, 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;
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) {
atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD);
atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD);
} else if (on_hold == BT_HFP_BTRH_ACCEPTED) {
if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE) &&
atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) {
if (bt_hf && bt_hf->accept) {
bt_hf->accept(hf->acl);
}
}
} else if (on_hold == BT_HFP_BTRH_REJECTED) {
if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_ACTIVE) &&
atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD)) {
if (bt_hf && bt_hf->reject) {
bt_hf->reject(hf->acl);
}
}
}
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 },
#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 },
};
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_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)
{
if (hf->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG) {
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);
}
return -ENOTSUP;
}
static int at_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 set (result %d) on %p", result, hf);
return 0;
}
#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */
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_CODEC_NEG)
{send_at_bac, at_bac_finish, false},
#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */
};
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);
}
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");
}
}
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;
}
slc_completed(hf_at);
return 0;
}
int cind_status_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;
}
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;
}
return 0;
}
int cind_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;
}
err = hfp_hf_send_cmd(hf, cind_status_resp, cind_status_finish,
"AT+CIND?");
if (err < 0) {
hf_slc_error(hf_at);
return err;
}
return 0;
}
int brsf_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;
}
err = hfp_hf_send_cmd(hf, cind_resp, cind_finish, "AT+CIND=?");
if (err < 0) {
hf_slc_error(hf_at);
return err;
}
return 0;
}
int hf_slc_establish(struct bt_hfp_hf *hf)
{
int err;
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;
}
return 0;
}
static struct bt_hfp_hf *bt_hfp_hf_lookup_bt_conn(struct bt_conn *conn)
{
int i;
for (i = 0; i < ARRAY_SIZE(bt_hfp_hf_pool); i++) {
struct bt_hfp_hf *hf = &bt_hfp_hf_pool[i];
if (hf->acl == conn) {
return hf;
}
}
return NULL;
}
#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_conn *conn, bool enable)
{
#if defined(CONFIG_BT_HFP_HF_CLI)
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
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_conn *conn, uint8_t gain)
{
#if defined(CONFIG_BT_HFP_HF_VOLUME)
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (gain > BT_HFP_HF_VGM_GAIN_MAX) {
LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX);
return -EINVAL;
}
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
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_conn *conn, uint8_t gain)
{
#if defined(CONFIG_BT_HFP_HF_VOLUME)
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (gain > BT_HFP_HF_VGM_GAIN_MAX) {
LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX);
return -EINVAL;
}
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
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->acl, (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_conn *conn)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
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 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_conn *conn)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) {
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(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD) &&
atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_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_conn *conn)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) {
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(hf->flags, BT_HFP_HF_FLAG_INCOMING_HELD) &&
atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_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_conn *conn)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP");
if (err < 0) {
LOG_ERR("Fail to terminate the active call on %p", hf);
}
return err;
}
int bt_hfp_hf_hold_incoming(struct bt_conn *conn)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INCOMING)) {
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_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);
if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD) &&
bt_hf->incoming_held) {
bt_hf->incoming_held(hf->acl);
}
return 0;
}
int bt_hfp_hf_query_respond_hold_status(struct bt_conn *conn)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_HOLD);
err = hfp_hf_send_cmd(hf, NULL, 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;
LOG_DBG("ATD (result %d) on %p", result, hf);
if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) {
LOG_WRN("No dialing call");
}
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->dialing) {
bt_hf->dialing(hf->acl, err);
}
return 0;
}
int bt_hfp_hf_number_call(struct bt_conn *conn, const char *number)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) {
LOG_ERR("Outgoing call is started");
return -EBUSY;
}
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_conn *conn, const char *location)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) {
LOG_ERR("Outgoing call is started");
return -EBUSY;
}
err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD>%s", location);
if (err < 0) {
LOG_ERR("Fail to start memory dialing 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;
LOG_DBG("AT+BLDN (result %d) on %p", result, hf);
if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) {
LOG_WRN("No dialing call");
}
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->dialing) {
bt_hf->dialing(hf->acl, err);
}
return 0;
}
int bt_hfp_hf_redial(struct bt_conn *conn)
{
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
if (!hf) {
LOG_ERR("No HF connection found");
return -ENOTCONN;
}
if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_DIALING)) {
LOG_ERR("Outgoing call is started");
return -EBUSY;
}
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_conn *conn)
{
#if defined(CONFIG_BT_HFP_HF_CODEC_NEG)
struct bt_hfp_hf *hf;
int err;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
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("BCC (result %d) on %p", result, hf);
return 0;
}
#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */
int bt_hfp_hf_select_codec(struct bt_conn *conn, uint8_t codec_id)
{
#if defined(CONFIG_BT_HFP_HF_CODEC_NEG)
struct bt_hfp_hf *hf;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
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("BCC (result %d) on %p", result, hf);
return 0;
}
#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */
int bt_hfp_hf_set_codecs(struct bt_conn *conn, uint8_t codec_ids)
{
#if defined(CONFIG_BT_HFP_HF_CODEC_NEG)
struct bt_hfp_hf *hf;
char ids[sizeof(hf->hf_codec_ids)*2*8 + 1];
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
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->acl, err);
}
return 0;
}
#endif /* CONFIG_BT_HFP_HF_ECNR */
int bt_hfp_hf_turn_off_ecnr(struct bt_conn *conn)
{
#if defined(CONFIG_BT_HFP_HF_ECNR)
struct bt_hfp_hf *hf;
LOG_DBG("");
if (!conn) {
LOG_ERR("Invalid connection");
return -ENOTCONN;
}
hf = bt_hfp_hf_lookup_bt_conn(conn);
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 */
}
static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc)
{
struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc);
LOG_DBG("hf connected");
BT_ASSERT(hf);
hf_slc_establish(hf);
}
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);
}
}
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);
if (at_parse_input(&hf->at, buf) < 0) {
LOG_ERR("Parsing failed");
}
}
static void hfp_hf_sent(struct bt_rfcomm_dlc *dlc, int err)
{
LOG_DBG("DLC %p sent cb (err %d)", dlc, err);
}
static int hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server,
struct bt_rfcomm_dlc **dlc)
{
int i;
static struct bt_rfcomm_dlc_ops ops = {
.connected = hfp_hf_connected,
.disconnected = hfp_hf_disconnected,
.recv = hfp_hf_recv,
.sent = hfp_hf_sent,
};
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;
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->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 supported codec ids */
hf->hf_codec_ids = BT_HFP_HF_SUPPORTED_CODEC_IDS;
k_fifo_init(&hf->tx_pending);
for (j = 0; j < HF_MAX_AG_INDICATORS; j++) {
hf->ind_table[j] = -1;
}
return 0;
}
LOG_ERR("Unable to establish HF connection (%p)", conn);
return -ENOMEM;
}
static void hfp_hf_sco_connected(struct bt_sco_chan *chan)
{
if ((bt_hf != NULL) && (bt_hf->sco_connected)) {
bt_hf->sco_connected(chan->sco->sco.acl, chan->sco);
}
}
static void hfp_hf_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason)
{
if ((bt_hf != NULL) && (bt_hf->sco_disconnected)) {
bt_hf->sco_disconnected(chan->sco, 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,
};
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;
}
LOG_ERR("Unable to establish HF connection (%p)", info->acl);
return -ENOMEM;
}
static void hfp_hf_init(void)
{
static struct bt_rfcomm_server chan = {
.channel = BT_RFCOMM_CHAN_HFP_HF,
.accept = hfp_hf_accept,
};
bt_rfcomm_server_register(&chan);
static struct bt_sco_server sco_server = {
.sec_level = BT_SECURITY_L0,
.accept = bt_hfp_hf_sco_accept,
};
bt_sco_server_register(&sco_server);
bt_sdp_register_service(&hfp_rec);
}
int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb)
{
if (!cb) {
return -EINVAL;
}
if (bt_hf) {
return -EALREADY;
}
bt_hf = cb;
hfp_hf_init();
return 0;
}