When `buf->len` is 0, the function of the while-loop will be returned with error code `-ENOTSUP`. The code block after while-loop cannot be reached event though it is a correct command. Use `buf->len` as the end condition of the while-loop. Fixes #74730. Signed-off-by: Lyle Zhu <lyle.zhu@nxp.com>
2360 lines
50 KiB
C
2360 lines
50 KiB
C
/*
|
|
* Copyright 2024 NXP
|
|
*
|
|
* 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_ag.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"
|
|
#include "hfp_ag_internal.h"
|
|
|
|
#define LOG_LEVEL CONFIG_BT_HFP_AG_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_hfp_ag);
|
|
|
|
typedef int (*bt_hfp_ag_parse_command_t)(struct bt_hfp_ag *ag, struct net_buf *buf);
|
|
|
|
struct bt_hfp_ag_at_cmd_handler {
|
|
const char *cmd;
|
|
bt_hfp_ag_parse_command_t handler;
|
|
};
|
|
|
|
static const struct {
|
|
const char *name;
|
|
const char *connector;
|
|
uint32_t min;
|
|
uint32_t max;
|
|
} ag_ind[] = {
|
|
{"service", "-", 0, 1}, /* BT_HFP_AG_SERVICE_IND */
|
|
{"call", ",", 0, 1}, /* BT_HFP_AG_CALL_IND */
|
|
{"callsetup", "-", 0, 3}, /* BT_HFP_AG_CALL_SETUP_IND */
|
|
{"callheld", "-", 0, 2}, /* BT_HFP_AG_CALL_HELD_IND */
|
|
{"signal", "-", 0, 5}, /* BT_HFP_AG_SIGNAL_IND */
|
|
{"roam", ",", 0, 1}, /* BT_HFP_AG_ROAM_IND */
|
|
{"battchg", "-", 0, 5} /* BT_HFP_AG_BATTERY_IND */
|
|
};
|
|
|
|
typedef void (*bt_hfp_ag_tx_cb_t)(struct bt_hfp_ag *ag, void *user_data);
|
|
|
|
struct bt_ag_tx {
|
|
sys_snode_t node;
|
|
|
|
struct bt_hfp_ag *ag;
|
|
struct net_buf *buf;
|
|
bt_hfp_ag_tx_cb_t cb;
|
|
void *user_data;
|
|
int err;
|
|
};
|
|
|
|
NET_BUF_POOL_FIXED_DEFINE(ag_pool, CONFIG_BT_HFP_AG_TX_BUF_COUNT,
|
|
BT_RFCOMM_BUF_SIZE(BT_HF_CLIENT_MAX_PDU),
|
|
CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);
|
|
|
|
static struct bt_hfp_ag bt_hfp_ag_pool[CONFIG_BT_MAX_CONN];
|
|
|
|
static struct bt_hfp_ag_cb *bt_ag;
|
|
|
|
/* 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;
|
|
|
|
static enum at_cme bt_hfp_ag_get_cme_err(int err)
|
|
{
|
|
enum at_cme cme_err;
|
|
|
|
switch (err) {
|
|
case -EOPNOTSUPP:
|
|
cme_err = CME_ERROR_OPERATION_NOT_SUPPORTED;
|
|
break;
|
|
case -EFAULT:
|
|
cme_err = CME_ERROR_AG_FAILURE;
|
|
break;
|
|
case -ENOSR:
|
|
cme_err = CME_ERROR_MEMORY_FAILURE;
|
|
break;
|
|
case -ENOMEM:
|
|
__fallthrough;
|
|
case -ENOBUFS:
|
|
cme_err = CME_ERROR_MEMORY_FULL;
|
|
break;
|
|
case -ENAMETOOLONG:
|
|
cme_err = CME_ERROR_DIAL_STRING_TO_LONG;
|
|
break;
|
|
case -EINVAL:
|
|
cme_err = CME_ERROR_INVALID_INDEX;
|
|
break;
|
|
case -ENOTSUP:
|
|
cme_err = CME_ERROR_OPERATION_NOT_ALLOWED;
|
|
break;
|
|
case -ENOTCONN:
|
|
cme_err = CME_ERROR_NO_CONNECTION_TO_PHONE;
|
|
break;
|
|
default:
|
|
cme_err = CME_ERROR_AG_FAILURE;
|
|
break;
|
|
}
|
|
|
|
return cme_err;
|
|
}
|
|
|
|
static void hfp_ag_lock(struct bt_hfp_ag *ag)
|
|
{
|
|
k_sem_take(&ag->lock, K_FOREVER);
|
|
}
|
|
|
|
static void hfp_ag_unlock(struct bt_hfp_ag *ag)
|
|
{
|
|
k_sem_give(&ag->lock);
|
|
}
|
|
|
|
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);
|
|
|
|
hfp_ag_lock(ag);
|
|
ag->state = state;
|
|
hfp_ag_unlock(ag);
|
|
|
|
switch (state) {
|
|
case BT_HFP_DISCONNECTED:
|
|
if (bt_ag && bt_ag->disconnected) {
|
|
bt_ag->disconnected(ag);
|
|
}
|
|
break;
|
|
case BT_HFP_CONNECTING:
|
|
break;
|
|
case BT_HFP_CONFIG:
|
|
break;
|
|
case BT_HFP_CONNECTED:
|
|
if (bt_ag && bt_ag->connected) {
|
|
bt_ag->connected(ag);
|
|
}
|
|
break;
|
|
case BT_HFP_DISCONNECTING:
|
|
break;
|
|
default:
|
|
LOG_WRN("no valid (%u) state was set", state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void bt_hfp_ag_set_call_state(struct bt_hfp_ag *ag, bt_hfp_call_state_t call_state)
|
|
{
|
|
bt_hfp_state_t state;
|
|
|
|
LOG_DBG("update call state %p, old %d -> new %d", ag, ag->call_state, call_state);
|
|
|
|
hfp_ag_lock(ag);
|
|
ag->call_state = call_state;
|
|
state = ag->state;
|
|
hfp_ag_unlock(ag);
|
|
|
|
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 (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 struct bt_ag_tx *bt_ag_tx_alloc(void)
|
|
{
|
|
/* The TX context always get freed in the system workqueue,
|
|
* so if we're in the same workqueue but there are no immediate
|
|
* contexts available, there's no chance we'll get one by waiting.
|
|
*/
|
|
if (k_current_get() == &k_sys_work_q.thread) {
|
|
return k_fifo_get(&ag_tx_free, K_NO_WAIT);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_HFP_AG_LOG_LEVEL_DBG)) {
|
|
struct bt_ag_tx *tx = k_fifo_get(&ag_tx_free, K_NO_WAIT);
|
|
|
|
if (tx) {
|
|
return tx;
|
|
}
|
|
|
|
LOG_WRN("Unable to get an immediate free bt_ag_tx");
|
|
}
|
|
|
|
return k_fifo_get(&ag_tx_free, K_FOREVER);
|
|
}
|
|
|
|
static void bt_ag_tx_free(struct bt_ag_tx *tx)
|
|
{
|
|
LOG_DBG("Free tx buffer %p", tx);
|
|
|
|
(void)memset(tx, 0, sizeof(*tx));
|
|
|
|
k_fifo_put(&ag_tx_free, tx);
|
|
}
|
|
|
|
static int hfp_ag_next_step(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *user_data)
|
|
{
|
|
struct bt_ag_tx *tx;
|
|
|
|
LOG_DBG("cb %p user_data %p", cb, user_data);
|
|
|
|
tx = bt_ag_tx_alloc();
|
|
if (tx == NULL) {
|
|
LOG_ERR("No tx buffers!");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
LOG_DBG("Alloc tx buffer %p", tx);
|
|
|
|
tx->ag = ag;
|
|
tx->buf = NULL;
|
|
tx->cb = cb;
|
|
tx->user_data = user_data;
|
|
tx->err = 0;
|
|
|
|
k_fifo_put(&ag_tx_notify, tx);
|
|
|
|
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;
|
|
va_list vargs;
|
|
int err;
|
|
bt_hfp_state_t state;
|
|
|
|
LOG_DBG("AG %p sending data cb %p user_data %p", ag, cb, user_data);
|
|
|
|
hfp_ag_lock(ag);
|
|
state = ag->state;
|
|
hfp_ag_unlock(ag);
|
|
if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) {
|
|
LOG_ERR("AG %p is not connected", ag);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
buf = bt_rfcomm_create_pdu(&ag_pool);
|
|
if (!buf) {
|
|
LOG_ERR("No Buffers!");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
tx = bt_ag_tx_alloc();
|
|
if (tx == NULL) {
|
|
LOG_ERR("No tx buffers!");
|
|
net_buf_unref(buf);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
LOG_DBG("buf %p tx %p", buf, tx);
|
|
|
|
tx->ag = ag;
|
|
tx->buf = buf;
|
|
tx->cb = cb;
|
|
tx->user_data = user_data;
|
|
|
|
va_start(vargs, format);
|
|
err = vsnprintk((char *)buf->data, (net_buf_tailroom(buf) - 1), format, vargs);
|
|
va_end(vargs);
|
|
|
|
if (err < 0) {
|
|
LOG_ERR("Unable to format variable arguments");
|
|
net_buf_unref(buf);
|
|
bt_ag_tx_free(tx);
|
|
return err;
|
|
}
|
|
|
|
net_buf_add(buf, err);
|
|
|
|
LOG_HEXDUMP_DBG(buf->data, buf->len, "Sending:");
|
|
|
|
hfp_ag_lock(ag);
|
|
sys_slist_append(&ag->tx_pending, &tx->node);
|
|
hfp_ag_unlock(ag);
|
|
|
|
/* Always active tx work */
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
ag->hf_codec_ids = codec_ids;
|
|
hfp_ag_unlock(ag);
|
|
|
|
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;
|
|
|
|
hfp_ag_lock(ag);
|
|
is_inband_ringtone = (ag->ag_features & BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false;
|
|
hfp_ag_unlock(ag);
|
|
|
|
if (is_inband_ringtone) {
|
|
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 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);
|
|
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);
|
|
} else {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
hfp_ag_unlock(ag);
|
|
|
|
if (is_char(buf, '?')) {
|
|
if (!is_char(buf, '\r')) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (!is_char(buf, '=')) {
|
|
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]);
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
atomic_set_bit_to(ag->flags, BT_HFP_AG_CMEE_ENABLE, cmee == 1);
|
|
|
|
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)
|
|
{
|
|
int err;
|
|
uint8_t old_value;
|
|
|
|
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);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ag->indicator_value[index] = value;
|
|
hfp_ag_unlock(ag);
|
|
|
|
LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value);
|
|
|
|
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 void hfp_ag_close_sco(struct bt_hfp_ag *ag)
|
|
{
|
|
struct bt_conn *sco;
|
|
|
|
LOG_DBG("");
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 (bt_ag && bt_ag->reject) {
|
|
bt_ag->reject(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)
|
|
{
|
|
hfp_ag_close_sco(ag);
|
|
bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_TERMINATE);
|
|
|
|
if (bt_ag && bt_ag->terminate) {
|
|
bt_ag->terminate(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 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;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
call_state = ag->call_state;
|
|
hfp_ag_unlock(ag);
|
|
|
|
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;
|
|
}
|
|
|
|
static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag *ag)
|
|
{
|
|
uint8_t status = HFP_AG_CLCC_STATUS_INVALID;
|
|
|
|
hfp_ag_lock(ag);
|
|
switch (ag->call_state) {
|
|
case BT_HFP_CALL_TERMINATE:
|
|
break;
|
|
case BT_HFP_CALL_OUTGOING:
|
|
status = HFP_AG_CLCC_STATUS_DIALING;
|
|
break;
|
|
case BT_HFP_CALL_INCOMING:
|
|
status = HFP_AG_CLCC_STATUS_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;
|
|
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;
|
|
uint8_t dir;
|
|
uint8_t status;
|
|
uint8_t mode;
|
|
uint8_t mpty;
|
|
|
|
if (!is_char(buf, '\r')) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
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;
|
|
}
|
|
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);
|
|
}
|
|
|
|
/* 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_accept_cb(struct bt_hfp_ag *ag, void *user_data)
|
|
{
|
|
bt_hfp_ag_set_call_state(ag, BT_HFP_CALL_ACTIVE);
|
|
|
|
if (bt_ag && bt_ag->accept) {
|
|
bt_ag->accept(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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
|
|
if ((bt_ag) && bt_ag->sco_disconnected) {
|
|
bt_ag->sco_disconnected(ag);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
bool create_sco;
|
|
|
|
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);
|
|
|
|
if (sco_conn == NULL) {
|
|
LOG_ERR("Fail to create sco connection!");
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
LOG_DBG("SCO connection created (%p)", sco_conn);
|
|
}
|
|
|
|
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_ERR("Codec is invalid");
|
|
hfp_ag_unlock(ag);
|
|
return -EINVAL;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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)) {
|
|
atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CONN);
|
|
err = bt_hfp_ag_codec_select(ag);
|
|
} else {
|
|
err = hfp_ag_open_sco(ag);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void bt_hfp_ag_audio_connection(struct bt_hfp_ag *ag, void *user_data)
|
|
{
|
|
int err;
|
|
|
|
err = bt_hfp_ag_create_audio_connection(ag);
|
|
if (err) {
|
|
bt_hfp_ag_unit_call_terminate(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 != 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);
|
|
}
|
|
}
|
|
|
|
static int bt_hfp_ag_ata_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
|
|
{
|
|
int err;
|
|
|
|
if (!is_char(buf, '\r')) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
if (ag->call_state != BT_HFP_CALL_ALERTING) {
|
|
hfp_ag_unlock(ag);
|
|
return -ENOTSUP;
|
|
}
|
|
hfp_ag_unlock(ag);
|
|
|
|
if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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", 0, 0, ag->operator);
|
|
if (err != 0) {
|
|
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)
|
|
{
|
|
int err;
|
|
|
|
if (!is_char(buf, '\r')) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
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;
|
|
}
|
|
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);
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN);
|
|
atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED);
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
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 (err != 0) {
|
|
LOG_ERR("Fail to send err :(%d)", 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;
|
|
size_t len;
|
|
|
|
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];
|
|
}
|
|
|
|
len = strlen(number);
|
|
if (len == 0) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) {
|
|
return -ENAMETOOLONG;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
if (ag->call_state != BT_HFP_CALL_TERMINATE) {
|
|
hfp_ag_unlock(ag);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static int bt_hfp_ag_bldn_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
|
|
{
|
|
int err;
|
|
|
|
if (!is_char(buf, '\r')) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
if (strlen(ag->number) == 0) {
|
|
hfp_ag_unlock(ag);
|
|
return -ENOSR;
|
|
}
|
|
|
|
if (ag->call_state != BT_HFP_CALL_TERMINATE) {
|
|
hfp_ag_unlock(ag);
|
|
return -EBUSY;
|
|
}
|
|
hfp_ag_unlock(ag);
|
|
|
|
err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, NULL);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bt_hfp_ag_clip_handler(struct bt_hfp_ag *ag, struct net_buf *buf)
|
|
{
|
|
int err;
|
|
uint32_t clip;
|
|
|
|
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 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)
|
|
{
|
|
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;
|
|
|
|
k_work_cancel_delayable(&ag->tx_work);
|
|
|
|
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);
|
|
hfp_ag_unlock(ag);
|
|
tx = CONTAINER_OF(node, struct bt_ag_tx, node);
|
|
}
|
|
|
|
bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTED);
|
|
|
|
hfp_ag_lock(ag);
|
|
call_state = ag->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);
|
|
}
|
|
|
|
LOG_DBG("AG %p", ag);
|
|
}
|
|
|
|
static void hfp_ag_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf)
|
|
{
|
|
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;
|
|
|
|
LOG_HEXDUMP_DBG(data, len, "Received:");
|
|
|
|
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 ((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 (err != 0) {
|
|
LOG_ERR("HFP AG send response err :(%d)", err);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
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 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;
|
|
|
|
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;
|
|
}
|
|
|
|
node = sys_slist_get(&ag->tx_pending);
|
|
hfp_ag_unlock(ag);
|
|
if (!node) {
|
|
LOG_ERR("No pending tx");
|
|
return;
|
|
}
|
|
|
|
tx = CONTAINER_OF(node, struct bt_ag_tx, node);
|
|
LOG_ERR("Completed pending tx %p", tx);
|
|
|
|
/* Restart the tx work */
|
|
k_work_reschedule(&ag->tx_work, K_NO_WAIT);
|
|
|
|
tx->err = err;
|
|
k_fifo_put(&ag_tx_notify, tx);
|
|
}
|
|
|
|
static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data)
|
|
{
|
|
int err;
|
|
bt_hfp_call_state_t call_state;
|
|
|
|
LOG_DBG("");
|
|
|
|
hfp_ag_lock(ag);
|
|
call_state = ag->call_state;
|
|
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;
|
|
}
|
|
}
|
|
|
|
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 *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, deferred_work);
|
|
|
|
(void)hfp_ag_next_step(ag, bt_ag_deferred_work_cb, NULL);
|
|
}
|
|
|
|
static void bt_ag_ringing_work_cb(struct bt_hfp_ag *ag, void *user_data)
|
|
{
|
|
int err;
|
|
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_ALERTING) {
|
|
|
|
if (!atomic_test_bit(ag->flags, BT_HFP_AG_INCOMING_CALL)) {
|
|
return;
|
|
}
|
|
|
|
k_work_reschedule(&ag->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 (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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
(void)hfp_ag_next_step(ag, bt_ag_ringing_work_cb, NULL);
|
|
}
|
|
|
|
int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t channel)
|
|
{
|
|
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,
|
|
};
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
*ag = NULL;
|
|
|
|
if (ag_thread_id == NULL) {
|
|
|
|
k_fifo_init(&ag_tx_free);
|
|
k_fifo_init(&ag_tx_notify);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ag_tx); i++) {
|
|
k_fifo_put(&ag_tx_free, &ag_tx[i]);
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bt_hfp_ag_pool); i++) {
|
|
struct bt_hfp_ag *_ag = &bt_hfp_ag_pool[i];
|
|
|
|
if (_ag->rfcomm_dlc.session) {
|
|
continue;
|
|
}
|
|
|
|
(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;
|
|
|
|
/* 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->deferred_work, bt_ag_deferred_work);
|
|
|
|
k_work_init_delayable(&_ag->ringing_work, bt_ag_ringing_work);
|
|
|
|
k_work_init_delayable(&_ag->tx_work, bt_ag_tx_work);
|
|
|
|
atomic_set_bit(_ag->flags, BT_HFP_AG_CODEC_CHANGED);
|
|
|
|
*ag = _ag;
|
|
}
|
|
|
|
if (*ag == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag)
|
|
{
|
|
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;
|
|
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);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
return bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc);
|
|
}
|
|
|
|
int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb)
|
|
{
|
|
if (!cb) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (bt_ag) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
bt_ag = cb;
|
|
|
|
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 err = 0;
|
|
size_t len;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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_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;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
/* Copy number to ag->number including null-character */
|
|
memcpy(ag->number, number, len + 1);
|
|
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);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_hfp_ag_reject(struct bt_hfp_ag *ag)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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;
|
|
}
|
|
|
|
err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE,
|
|
bt_hfp_ag_reject_cb, NULL);
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_hfp_ag_accept(struct bt_hfp_ag *ag)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_hfp_ag_terminate(struct bt_hfp_ag *ag)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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_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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number)
|
|
{
|
|
int err = 0;
|
|
size_t len;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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_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;
|
|
}
|
|
|
|
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);
|
|
} else {
|
|
bt_hfp_ag_call_ringing_cb(ag, false);
|
|
}
|
|
}
|
|
|
|
int bt_hfp_ag_remote_ringing(struct bt_hfp_ag *ag)
|
|
{
|
|
int err = 0;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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_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);
|
|
|
|
err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND,
|
|
BT_HFP_CALL_SETUP_REMOTE_ALERTING, bt_hfp_ag_ringing_cb,
|
|
NULL);
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_hfp_ag_remote_reject(struct bt_hfp_ag *ag)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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_OUTGOING)) {
|
|
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_SETUP_IND, BT_HFP_CALL_SETUP_NONE,
|
|
bt_hfp_ag_reject_cb, NULL);
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_hfp_ag_remote_accept(struct bt_hfp_ag *ag)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_hfp_ag_remote_terminate(struct bt_hfp_ag *ag)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
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_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;
|
|
}
|
|
|
|
int bt_hfp_ag_set_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, uint8_t value)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
if (ag->state != BT_HFP_CONNECTED) {
|
|
hfp_ag_unlock(ag);
|
|
return -ENOTCONN;
|
|
}
|
|
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, index, value, NULL, NULL);
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, char *name)
|
|
{
|
|
int len;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
if (ag->state != BT_HFP_CONNECTED) {
|
|
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;
|
|
}
|
|
|
|
int bt_hfp_ag_select_codec(struct bt_hfp_ag *ag, uint8_t id)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
if (ag == NULL) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
if (ag->state != BT_HFP_CONNECTED) {
|
|
hfp_ag_unlock(ag);
|
|
return -ENOTCONN;
|
|
}
|
|
|
|
if (!(ag->hf_codec_ids && BIT(id))) {
|
|
hfp_ag_unlock(ag);
|
|
return -ENOTSUP;
|
|
}
|
|
hfp_ag_unlock(ag);
|
|
|
|
if (atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
hfp_ag_lock(ag);
|
|
ag->selected_codec_id = id;
|
|
hfp_ag_unlock(ag);
|
|
|
|
atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED);
|
|
|
|
err = bt_hfp_ag_create_audio_connection(ag);
|
|
|
|
return err;
|
|
}
|