blob: c9f92f1618cf4cb025440cdac7bf4ae9e6d56665 [file] [log] [blame]
/*
* 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;
}