| /* |
| * 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 <zephyr/bluetooth/classic/sdp.h> |
| |
| #include "host/hci_core.h" |
| #include "host/conn_internal.h" |
| #include "l2cap_br_internal.h" |
| #include "rfcomm_internal.h" |
| #include "at.h" |
| #include "sco_internal.h" |
| #include "hfp_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; |
| |
| #define AG_SUPT_FEAT(ag, _feature) ((ag->ag_features & (_feature)) != 0) |
| #define HF_SUPT_FEAT(ag, _feature) ((ag->hf_features & (_feature)) != 0) |
| #define BOTH_SUPT_FEAT(ag, _hf_feature, _ag_feature) \ |
| (HF_SUPT_FEAT(ag, _hf_feature) && AG_SUPT_FEAT(ag, _ag_feature)) |
| |
| /* Sent but not acknowledged TX packets with a callback */ |
| static struct bt_ag_tx ag_tx[CONFIG_BT_HFP_AG_TX_BUF_COUNT * 2]; |
| static K_FIFO_DEFINE(ag_tx_free); |
| static K_FIFO_DEFINE(ag_tx_notify); |
| |
| /* HFP Gateway SDP record */ |
| static struct bt_sdp_attribute hfp_ag_attrs[] = { |
| BT_SDP_NEW_SERVICE, |
| BT_SDP_LIST( |
| BT_SDP_ATTR_SVCLASS_ID_LIST, |
| BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), |
| BT_SDP_DATA_ELEM_LIST( |
| { |
| BT_SDP_TYPE_SIZE(BT_SDP_UUID16), |
| BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_AGW_SVCLASS) |
| }, |
| { |
| BT_SDP_TYPE_SIZE(BT_SDP_UUID16), |
| BT_SDP_ARRAY_16(BT_SDP_GENERIC_AUDIO_SVCLASS) |
| } |
| ) |
| ), |
| BT_SDP_LIST( |
| BT_SDP_ATTR_PROTO_DESC_LIST, |
| BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), |
| BT_SDP_DATA_ELEM_LIST( |
| { |
| BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), |
| BT_SDP_DATA_ELEM_LIST( |
| { |
| BT_SDP_TYPE_SIZE(BT_SDP_UUID16), |
| BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) |
| }, |
| ) |
| }, |
| { |
| BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), |
| BT_SDP_DATA_ELEM_LIST( |
| { |
| BT_SDP_TYPE_SIZE(BT_SDP_UUID16), |
| BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) |
| }, |
| { |
| BT_SDP_TYPE_SIZE(BT_SDP_UINT8), |
| BT_SDP_ARRAY_8(BT_RFCOMM_CHAN_HFP_AG) |
| }, |
| ) |
| }, |
| ) |
| ), |
| BT_SDP_LIST( |
| BT_SDP_ATTR_PROFILE_DESC_LIST, |
| BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), |
| BT_SDP_DATA_ELEM_LIST( |
| { |
| BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), |
| BT_SDP_DATA_ELEM_LIST( |
| { |
| BT_SDP_TYPE_SIZE(BT_SDP_UUID16), |
| BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_SVCLASS) |
| }, |
| { |
| BT_SDP_TYPE_SIZE(BT_SDP_UINT16), |
| BT_SDP_ARRAY_16(0x0109) |
| }, |
| ) |
| }, |
| ) |
| ), |
| |
| BT_SDP_LIST( |
| BT_SDP_ATTR_NETWORK, |
| BT_SDP_TYPE_SIZE(BT_SDP_UINT8), |
| BT_SDP_ARRAY_8(IS_ENABLED(CONFIG_BT_HFP_AG_REJECT_CALL)) |
| ), |
| /* The values of the “SupportedFeatures” bitmap shall be the same as the |
| * values of the Bits 0 to 4 of the AT-command AT+BRSF (see Section 5.3). |
| */ |
| BT_SDP_SUPPORTED_FEATURES(BT_HFP_AG_SDP_SUPPORTED_FEATURES), |
| }; |
| |
| static struct bt_sdp_record hfp_ag_rec = BT_SDP_RECORD(hfp_ag_attrs); |
| |
| static enum at_cme bt_hfp_ag_get_cme_err(int err) |
| { |
| enum at_cme cme_err; |
| |
| switch (err) { |
| case -ENOEXEC: |
| 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: |
| 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) |
| { |
| bt_hfp_state_t old_state; |
| |
| hfp_ag_lock(ag); |
| old_state = ag->state; |
| ag->state = state; |
| hfp_ag_unlock(ag); |
| |
| LOG_DBG("update state %p, old %d -> new %d", ag, old_state, state); |
| |
| if (old_state == state) { |
| return; |
| } |
| |
| switch (state) { |
| case BT_HFP_DISCONNECTED: |
| if (bt_ag && bt_ag->disconnected) { |
| 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->acl_conn, ag); |
| } |
| break; |
| case BT_HFP_DISCONNECTING: |
| break; |
| default: |
| LOG_WRN("no valid (%u) state was set", state); |
| break; |
| } |
| } |
| |
| static struct bt_hfp_ag_call *get_call_from_number(struct bt_hfp_ag *ag, const char *number, |
| uint8_t type) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if (call->type != type) { |
| continue; |
| } |
| |
| if (strcmp(call->number, number)) { |
| continue; |
| } |
| |
| return call; |
| } |
| |
| return NULL; |
| } |
| |
| static void bt_ag_deferred_work(struct k_work *work); |
| static void bt_ag_ringing_work(struct k_work *work); |
| |
| static struct bt_hfp_ag_call *get_new_call(struct bt_hfp_ag *ag, const char *number, uint8_t type) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_and_set_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| memset(call->number, 0, sizeof(call->number)); |
| if (number != NULL) { |
| strncpy(call->number, number, sizeof(call->number) - 1); |
| } |
| call->type = type; |
| call->ag = ag; |
| k_work_init_delayable(&call->deferred_work, bt_ag_deferred_work); |
| k_work_init_delayable(&call->ringing_work, bt_ag_ringing_work); |
| return call; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int get_none_released_calls(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| int count = 0; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| count++; |
| } |
| } |
| |
| return count; |
| } |
| |
| static struct bt_ag_tx *bt_ag_tx_alloc(void) |
| { |
| /* 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_data(struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *user_data, |
| const char *format, ...) |
| { |
| struct net_buf *buf = NULL; |
| struct bt_ag_tx *tx = NULL; |
| 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); |
| |
| state = ag->state; |
| if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) { |
| LOG_ERR("AG %p is not connected", ag); |
| err = -ENOTCONN; |
| goto failed; |
| } |
| |
| buf = bt_rfcomm_create_pdu(&ag_pool); |
| if (!buf) { |
| LOG_ERR("No Buffers!"); |
| err = -ENOMEM; |
| goto failed; |
| } |
| |
| tx = bt_ag_tx_alloc(); |
| if (tx == NULL) { |
| LOG_ERR("No tx buffers!"); |
| err = -ENOMEM; |
| goto failed; |
| } |
| |
| 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"); |
| goto failed; |
| } |
| |
| 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; |
| |
| failed: |
| if (buf) { |
| net_buf_unref(buf); |
| } |
| |
| if (tx) { |
| bt_ag_tx_free(tx); |
| } |
| bt_hfp_ag_disconnect(ag); |
| return err; |
| } |
| |
| 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; |
| |
| if (index >= BT_HFP_AG_IND_MAX) { |
| return -EINVAL; |
| } |
| |
| if ((ag_ind[index].max < value) || (ag_ind[index].min > value)) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| old_value = ag->indicator_value[index]; |
| if (value == old_value) { |
| LOG_ERR("Duplicate value setting, indicator %d, old %d -> new %d", index, old_value, |
| value); |
| hfp_ag_unlock(ag); |
| if (cb) { |
| cb(ag, user_data); |
| } |
| return -EINVAL; |
| } |
| |
| ag->indicator_value[index] = value; |
| hfp_ag_unlock(ag); |
| |
| LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); |
| |
| if (!(ag->indicator & BIT(index))) { |
| LOG_INF("The indicator %d is deactivated", index); |
| /* If the indicator is deactivated, consider it a successful set. */ |
| return 0; |
| } |
| |
| err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", (uint8_t)index + 1, value); |
| if (err) { |
| hfp_ag_lock(ag); |
| ag->indicator_value[index] = old_value; |
| hfp_ag_unlock(ag); |
| LOG_ERR("Fail to update indicator %d, current %d", index, old_value); |
| } |
| |
| return err; |
| } |
| |
| static int hfp_ag_force_update_indicator(struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, |
| uint8_t value, bt_hfp_ag_tx_cb_t cb, void *user_data) |
| { |
| int err; |
| uint8_t old_value; |
| |
| hfp_ag_lock(ag); |
| old_value = ag->indicator_value[index]; |
| 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 clear_call_setup_ind_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| uint8_t call_ind; |
| int err; |
| |
| hfp_ag_lock(ag); |
| call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; |
| hfp_ag_unlock(ag); |
| |
| if (call_ind) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to clear call_setup ind"); |
| } |
| } |
| } |
| |
| static void clear_call_ind_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| uint8_t call_setup_ind; |
| int err; |
| |
| hfp_ag_lock(ag); |
| call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; |
| hfp_ag_unlock(ag); |
| |
| if (call_setup_ind) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to clear call_setup ind"); |
| } |
| } |
| } |
| |
| static void clear_call_and_call_setup_ind(struct bt_hfp_ag *ag) |
| { |
| uint8_t call_setup_ind; |
| uint8_t call_ind; |
| int err; |
| |
| hfp_ag_lock(ag); |
| call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; |
| call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; |
| hfp_ag_unlock(ag); |
| |
| if (call_setup_ind) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| clear_call_setup_ind_cb, NULL); |
| if (err) { |
| LOG_ERR("Fail to clear call_setup ind"); |
| } |
| } |
| |
| if (call_ind) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, clear_call_ind_cb, NULL); |
| if (err) { |
| LOG_ERR("Fail to clear call ind"); |
| } |
| } |
| } |
| |
| static int get_active_calls(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| int count = 0; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| hfp_ag_lock(ag); |
| if ((call->call_state == BT_HFP_CALL_ACTIVE) && |
| (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { |
| count++; |
| } |
| hfp_ag_unlock(ag); |
| } |
| } |
| |
| return count; |
| } |
| |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| static int get_active_held_calls(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| int count = 0; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| hfp_ag_lock(ag); |
| if ((call->call_state == BT_HFP_CALL_HOLD) || |
| ((call->call_state == BT_HFP_CALL_ACTIVE) && |
| (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)))) { |
| count++; |
| } |
| hfp_ag_unlock(ag); |
| } |
| } |
| |
| return count; |
| } |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| |
| static int get_held_calls(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| int count = 0; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| hfp_ag_lock(ag); |
| if ((call->call_state == BT_HFP_CALL_ACTIVE) && |
| (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { |
| count++; |
| } else if (call->call_state == BT_HFP_CALL_HOLD) { |
| count++; |
| } |
| hfp_ag_unlock(ag); |
| } |
| } |
| |
| return count; |
| } |
| |
| static void ag_notify_call_held_ind(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int active_call_count; |
| int held_call_count; |
| int err; |
| uint8_t value; |
| |
| active_call_count = get_active_calls(ag); |
| held_call_count = get_held_calls(ag); |
| if (active_call_count && held_call_count) { |
| value = BT_HFP_CALL_HELD_ACTIVE_HELD; |
| } else if (held_call_count) { |
| value = BT_HFP_CALL_HELD_HELD; |
| } else { |
| value = BT_HFP_CALL_HELD_NONE; |
| } |
| |
| err = hfp_ag_force_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, value, NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to notify call_held ind :(%d)", err); |
| } |
| } |
| |
| static void release_call(struct bt_hfp_ag_call *call) |
| { |
| atomic_clear_bit(call->flags, BT_HFP_AG_CALL_IN_USING); |
| k_work_cancel_delayable(&call->ringing_work); |
| k_work_cancel_delayable(&call->deferred_work); |
| /* |
| * Because it is uncertain whether `ringing_work` and `deferred_work` have been cancelled, |
| * do not clear ringing_work and deferred_work. |
| * Use `offsetof()` to get the offset of `ringing_work` to avoid access the fields |
| * `ringing_work` and `deferred_work`. |
| */ |
| (void)memset(call, 0, offsetof(struct bt_hfp_ag_call, ringing_work)); |
| } |
| |
| static void free_call(struct bt_hfp_ag_call *call) |
| { |
| int call_count; |
| struct bt_hfp_ag *ag; |
| int err; |
| |
| ag = call->ag; |
| |
| release_call(call); |
| |
| call_count = get_none_released_calls(ag); |
| |
| if (!call_count) { |
| clear_call_and_call_setup_ind(ag); |
| } else { |
| err = hfp_ag_next_step(ag, ag_notify_call_held_ind, NULL); |
| if (err) { |
| LOG_ERR("Fail to notify call_held ind :(%d)", err); |
| } |
| } |
| } |
| |
| static void bt_hfp_ag_set_call_state(struct bt_hfp_ag_call *call, bt_hfp_call_state_t call_state) |
| { |
| bt_hfp_state_t state; |
| struct bt_hfp_ag *ag = call->ag; |
| |
| LOG_DBG("update call state %p, old %d -> new %d", call, call->call_state, call_state); |
| |
| hfp_ag_lock(ag); |
| call->call_state = call_state; |
| state = ag->state; |
| hfp_ag_unlock(ag); |
| |
| switch (call_state) { |
| case BT_HFP_CALL_TERMINATE: |
| atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); |
| free_call(call); |
| break; |
| case BT_HFP_CALL_OUTGOING: |
| k_work_reschedule(&call->deferred_work, |
| K_SECONDS(CONFIG_BT_HFP_AG_OUTGOING_TIMEOUT)); |
| break; |
| case BT_HFP_CALL_INCOMING: |
| k_work_reschedule(&call->deferred_work, |
| K_SECONDS(CONFIG_BT_HFP_AG_INCOMING_TIMEOUT)); |
| break; |
| case BT_HFP_CALL_ALERTING: |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { |
| k_work_reschedule(&call->ringing_work, K_NO_WAIT); |
| } else { |
| k_work_cancel_delayable(&call->ringing_work); |
| } |
| k_work_reschedule(&call->deferred_work, |
| K_SECONDS(CONFIG_BT_HFP_AG_ALERTING_TIMEOUT)); |
| break; |
| case BT_HFP_CALL_ACTIVE: |
| k_work_cancel_delayable(&call->ringing_work); |
| k_work_cancel_delayable(&call->deferred_work); |
| break; |
| case BT_HFP_CALL_HOLD: |
| break; |
| default: |
| /* Invalid state */ |
| break; |
| } |
| |
| if (state == BT_HFP_DISCONNECTING) { |
| int err = bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); |
| |
| if (err) { |
| LOG_ERR("Fail to disconnect DLC %p", &ag->rfcomm_dlc); |
| } |
| } |
| } |
| |
| static void hfp_ag_close_sco(struct bt_hfp_ag *ag) |
| { |
| struct bt_conn *sco = NULL; |
| int call_count; |
| |
| LOG_DBG(""); |
| |
| hfp_ag_lock(ag); |
| call_count = get_none_released_calls(ag); |
| if (call_count > 0) { |
| LOG_INF("Do not close sco because not all calls are released"); |
| hfp_ag_unlock(ag); |
| return; |
| } |
| |
| if (ag->sco_conn != NULL) { |
| bt_conn_unref(ag->sco_conn); |
| ag->sco_conn = NULL; |
| sco = ag->sco_chan.sco; |
| } |
| hfp_ag_unlock(ag); |
| |
| if (sco != NULL) { |
| LOG_DBG("Disconnect sco %p", sco); |
| bt_conn_disconnect(sco, BT_HCI_ERR_LOCALHOST_TERM_CONN); |
| } |
| } |
| |
| static void ag_reject_call(struct bt_hfp_ag_call *call) |
| { |
| struct bt_hfp_ag *ag; |
| |
| ag = call->ag; |
| |
| if (bt_ag && bt_ag->reject) { |
| bt_ag->reject(call); |
| } |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { |
| LOG_DBG("It is not audio connection"); |
| hfp_ag_close_sco(ag); |
| } |
| } |
| |
| static void ag_terminate_call(struct bt_hfp_ag_call *call) |
| { |
| struct bt_hfp_ag *ag; |
| |
| ag = call->ag; |
| |
| if (bt_ag && bt_ag->terminate) { |
| bt_ag->terminate(call); |
| } |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { |
| LOG_DBG("It is not audio connection"); |
| hfp_ag_close_sco(ag); |
| } |
| } |
| |
| static int hfp_ag_send(struct bt_hfp_ag *ag, struct bt_ag_tx *tx) |
| { |
| int err = bt_rfcomm_dlc_send(&ag->rfcomm_dlc, tx->buf); |
| |
| if (err < 0) { |
| net_buf_unref(tx->buf); |
| } |
| |
| return err; |
| } |
| |
| static void bt_ag_tx_work(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, tx_work); |
| sys_snode_t *node; |
| struct bt_ag_tx *tx; |
| bt_hfp_state_t state; |
| |
| hfp_ag_lock(ag); |
| state = ag->state; |
| if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) { |
| LOG_ERR("AG %p is not connected", ag); |
| goto unlock; |
| } |
| |
| node = sys_slist_peek_head(&ag->tx_pending); |
| if (!node) { |
| LOG_DBG("No pending tx"); |
| goto unlock; |
| } |
| |
| tx = CONTAINER_OF(node, struct bt_ag_tx, node); |
| |
| if (!atomic_test_and_set_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { |
| LOG_DBG("AG %p sending tx %p", ag, tx); |
| int err = hfp_ag_send(ag, tx); |
| |
| if (err < 0) { |
| LOG_ERR("Rfcomm send error :(%d)", err); |
| sys_slist_find_and_remove(&ag->tx_pending, &tx->node); |
| tx->err = err; |
| k_fifo_put(&ag_tx_notify, tx); |
| /* Clear the tx ongoing flag */ |
| if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) { |
| LOG_WRN("tx ongoing flag is not set"); |
| } |
| /* Due to the work is failed, restart the tx work */ |
| k_work_reschedule(&ag->tx_work, K_NO_WAIT); |
| } |
| } |
| |
| unlock: |
| hfp_ag_unlock(ag); |
| } |
| |
| static void skip_space(struct net_buf *buf) |
| { |
| size_t count = 0; |
| |
| while ((buf->len > count) && (buf->data[count] == ' ')) { |
| count++; |
| } |
| |
| if (count > 0) { |
| (void)net_buf_pull(buf, count); |
| } |
| } |
| |
| static int get_number(struct net_buf *buf, uint32_t *number) |
| { |
| int err = -EINVAL; |
| |
| *number = 0; |
| |
| skip_space(buf); |
| while (buf->len > 0) { |
| if ((buf->data[0] >= '0') && (buf->data[0] <= '9')) { |
| *number = *number * 10 + buf->data[0] - '0'; |
| (void)net_buf_pull(buf, 1); |
| err = 0; |
| } else { |
| break; |
| } |
| } |
| skip_space(buf); |
| |
| return err; |
| } |
| |
| static int get_char(struct net_buf *buf, char *c) |
| { |
| int err = -EINVAL; |
| |
| skip_space(buf); |
| if (buf->len > 0) { |
| *c = (char)buf->data[0]; |
| (void)net_buf_pull(buf, 1); |
| err = 0; |
| } |
| skip_space(buf); |
| |
| return err; |
| } |
| |
| static bool is_char(struct net_buf *buf, uint8_t c) |
| { |
| bool found = false; |
| |
| skip_space(buf); |
| if (buf->len > 0) { |
| if (buf->data[0] == c) { |
| (void)net_buf_pull(buf, 1); |
| found = true; |
| } |
| } |
| skip_space(buf); |
| |
| return found; |
| } |
| |
| static int bt_hfp_ag_brsf_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| uint32_t hf_features; |
| int err; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &hf_features); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| ag->hf_features = hf_features; |
| |
| return hfp_ag_send_data(ag, NULL, NULL, "\r\n+BRSF:%d\r\n", ag->ag_features); |
| } |
| |
| static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| uint32_t codec; |
| uint32_t codec_ids = 0U; |
| int err = 0; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_CODEC_NEG, BT_HFP_AG_FEATURE_CODEC_NEG)) { |
| return -ENOEXEC; |
| } |
| |
| while (buf->len > 0) { |
| err = get_number(buf, &codec); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, ',')) { |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| } |
| |
| if (codec < NUM_BITS(sizeof(codec_ids))) { |
| codec_ids |= BIT(codec); |
| } |
| } |
| |
| if (!(codec_ids & BIT(BT_HFP_AG_CODEC_CVSD))) { |
| return -ENOEXEC; |
| } |
| |
| hfp_ag_lock(ag); |
| ag->hf_codec_ids = codec_ids; |
| ag->selected_codec_id = 0; |
| hfp_ag_unlock(ag); |
| |
| atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); |
| |
| if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { |
| /* Codec connection is ended. It needs to be restarted. */ |
| LOG_DBG("Codec connection is ended. It needs to be restarted."); |
| if (bt_ag && bt_ag->codec_negotiate) { |
| bt_ag->codec_negotiate(ag, -EAGAIN); |
| } |
| } |
| |
| if (bt_ag && bt_ag->codec) { |
| bt_ag->codec(ag, ag->hf_codec_ids); |
| } |
| |
| return 0; |
| } |
| |
| static int bt_hfp_ag_get_ongoing_calls(struct bt_hfp_ag *ag) |
| { |
| int err; |
| |
| ag->ongoing_call_count = 0; |
| |
| if ((bt_ag == NULL) || (bt_ag->get_ongoing_call == NULL)) { |
| LOG_DBG("No ongoing call retrieval method available"); |
| return -EINVAL; |
| } |
| |
| if (ag->state == BT_HFP_CONNECTED) { |
| LOG_ERR("Only works during SLC establishment phase"); |
| return -EINVAL; |
| } |
| |
| err = bt_ag->get_ongoing_call(ag); |
| if (err != 0) { |
| LOG_DBG("No ongoing call retrieved"); |
| } |
| |
| return err; |
| } |
| |
| static int bt_hfp_ag_notify_cind_value(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_ongoing_call *call; |
| uint8_t call_value = 0; |
| uint8_t call_setup_value = 0; |
| uint8_t call_held_value = 0; |
| uint8_t call_active_count = 0; |
| uint8_t call_held_count = 0; |
| |
| if (ag->ongoing_call_count == 0) { |
| return hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%u,%u,%u,%u,%u,%u,%u\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]); |
| } |
| |
| for (size_t index = 0; index < ag->ongoing_call_count; index++) { |
| call = &ag->ongoing_calls[index]; |
| |
| switch (call->status) { |
| case BT_HFP_AG_CALL_STATUS_ACTIVE: |
| call_value = 1; |
| call_active_count++; |
| break; |
| case BT_HFP_AG_CALL_STATUS_HELD: |
| call_value = 1; |
| call_held_count++; |
| break; |
| case BT_HFP_AG_CALL_STATUS_DIALING: |
| call_setup_value = BT_HFP_CALL_SETUP_OUTGOING; |
| break; |
| case BT_HFP_AG_CALL_STATUS_ALERTING: |
| call_setup_value = BT_HFP_CALL_SETUP_REMOTE_ALERTING; |
| break; |
| case BT_HFP_AG_CALL_STATUS_INCOMING: |
| call_setup_value = BT_HFP_CALL_SETUP_INCOMING; |
| break; |
| case BT_HFP_AG_CALL_STATUS_WAITING: |
| call_setup_value = BT_HFP_CALL_SETUP_INCOMING; |
| break; |
| case BT_HFP_AG_CALL_STATUS_INCOMING_HELD: |
| call_value = 1; |
| break; |
| } |
| } |
| |
| if ((call_active_count != 0) && (call_held_count != 0)) { |
| call_held_value = BT_HFP_CALL_HELD_ACTIVE_HELD; |
| } else if (call_held_count != 0) { |
| call_held_value = BT_HFP_CALL_HELD_HELD; |
| } else { |
| call_held_value = BT_HFP_CALL_HELD_NONE; |
| } |
| |
| return hfp_ag_send_data(ag, NULL, NULL, "\r\n+CIND:%u,%u,%u,%u,%u,%u,%u\r\n", |
| ag->indicator_value[BT_HFP_AG_SERVICE_IND], |
| call_value, |
| call_setup_value, |
| call_held_value, |
| ag->indicator_value[BT_HFP_AG_SIGNAL_IND], |
| ag->indicator_value[BT_HFP_AG_ROAM_IND], |
| ag->indicator_value[BT_HFP_AG_BATTERY_IND]); |
| } |
| |
| 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 = bt_hfp_ag_get_ongoing_calls(ag); |
| if (err != 0) { |
| err = bt_hfp_ag_notify_cind_value(ag); |
| } else { |
| err = -EINPROGRESS; |
| atomic_set_bit(ag->flags, BT_HGP_AG_ONGOING_CALLS); |
| k_work_reschedule(&ag->ongoing_call_work, |
| K_MSEC(CONFIG_BT_HFP_AG_GET_ONGOING_CALL_TIMEOUT)); |
| } |
| } |
| |
| return err; |
| } |
| |
| static void bt_hfp_ag_update_call_flags(struct bt_hfp_ag_call *call, enum bt_hfp_ag_call_dir dir) |
| { |
| int call_count; |
| |
| call_count = get_none_released_calls(call->ag); |
| |
| atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_INCOMING, dir == BT_HFP_AG_CALL_DIR_INCOMING); |
| |
| if (call_count > 1) { |
| if (dir == BT_HFP_AG_CALL_DIR_INCOMING) { |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY); |
| } else { |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY); |
| } |
| } |
| } |
| |
| static void bt_hfp_ag_notify_new_call(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call) |
| { |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) || |
| atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { |
| if (bt_ag && bt_ag->incoming) { |
| bt_ag->incoming(ag, call, call->number); |
| } |
| } else { |
| if (bt_ag && bt_ag->outgoing) { |
| bt_ag->outgoing(ag, call, call->number); |
| } |
| } |
| } |
| |
| static void bt_hfp_ag_add_active_call(struct bt_hfp_ag *ag, |
| struct bt_hfp_ag_ongoing_call *ongoing_call) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| call = get_new_call(ag, ongoing_call->number, ongoing_call->type); |
| if (call == NULL) { |
| return; |
| } |
| |
| LOG_DBG("Call %p is active", call); |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); |
| bt_hfp_ag_update_call_flags(call, ongoing_call->dir); |
| bt_hfp_ag_notify_new_call(ag, call); |
| |
| if (bt_ag && bt_ag->accept) { |
| bt_ag->accept(call); |
| } |
| } |
| |
| static void bt_hfp_ag_add_held_call(struct bt_hfp_ag *ag, |
| struct bt_hfp_ag_ongoing_call *ongoing_call) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| call = get_new_call(ag, ongoing_call->number, ongoing_call->type); |
| if (call == NULL) { |
| return; |
| } |
| |
| LOG_DBG("Call %p is held", call); |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); |
| bt_hfp_ag_update_call_flags(call, ongoing_call->dir); |
| bt_hfp_ag_notify_new_call(ag, call); |
| |
| if (bt_ag && bt_ag->held) { |
| bt_ag->held(call); |
| } |
| } |
| |
| static void bt_hfp_ag_add_incoming_held_call(struct bt_hfp_ag *ag, |
| struct bt_hfp_ag_ongoing_call *ongoing_call) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| call = get_new_call(ag, ongoing_call->number, ongoing_call->type); |
| if (call == NULL) { |
| return; |
| } |
| |
| LOG_DBG("Incoming call %p is held", call); |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); |
| bt_hfp_ag_update_call_flags(call, ongoing_call->dir); |
| bt_hfp_ag_notify_new_call(ag, call); |
| |
| if (bt_ag && bt_ag->incoming_held) { |
| bt_ag->incoming_held(call); |
| } |
| } |
| |
| static struct bt_hfp_ag_call *get_call_with_flag(struct bt_hfp_ag *ag, int bit) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| ARRAY_FOR_EACH(ag->calls, i) { |
| call = &ag->calls[i]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if (atomic_test_bit(call->flags, bit)) { |
| return call; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct bt_hfp_ag_call *get_call_clear_flag(struct bt_hfp_ag *ag, int bit) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| call = get_call_with_flag(ag, bit); |
| if (call != NULL) { |
| atomic_clear_bit(call->flags, bit); |
| } |
| |
| return call; |
| } |
| |
| static struct bt_hfp_ag_call *get_call_with_flag_and_state(struct bt_hfp_ag *ag, int bit, |
| bt_hfp_call_state_t state) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| ARRAY_FOR_EACH(ag->calls, i) { |
| call = &ag->calls[i]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if ((call->call_state & state) && atomic_test_bit(call->flags, bit)) { |
| return call; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| if (call) { |
| ag_reject_call(call); |
| } |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { |
| LOG_DBG("It is not audio connection"); |
| hfp_ag_close_sco(ag); |
| } |
| } |
| |
| static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_reject_cb, user_data); |
| if (err != 0) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| |
| static void bt_hfp_ag_call_ringing_cb(struct bt_hfp_ag_call *call, bool in_bond) |
| { |
| if (bt_ag && bt_ag->ringing) { |
| bt_ag->ringing(call, in_bond); |
| } |
| } |
| |
| static void hfp_ag_sco_valid_call_update(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| call = get_call_clear_flag(ag, BT_HFP_AG_CALL_OPEN_SCO); |
| |
| if (call == NULL) { |
| return; |
| } |
| |
| if ((call->call_state == BT_HFP_CALL_INCOMING) || |
| atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); |
| bt_hfp_ag_call_ringing_cb(call, true); |
| } |
| } |
| |
| 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); |
| |
| if (ag->sco_conn == NULL) { |
| ag->sco_conn = bt_conn_ref(chan->sco); |
| } |
| |
| hfp_ag_sco_valid_call_update(ag); |
| |
| if ((bt_ag) && bt_ag->sco_connected) { |
| bt_ag->sco_connected(ag, chan->sco); |
| } |
| } |
| |
| static void hfp_ag_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason) |
| { |
| struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); |
| bt_hfp_call_state_t call_state; |
| struct bt_hfp_ag_call *call; |
| |
| call = get_call_clear_flag(ag, BT_HFP_AG_CALL_OPEN_SCO); |
| |
| if ((bt_ag != NULL) && bt_ag->sco_disconnected) { |
| bt_ag->sco_disconnected(chan->sco, reason); |
| } |
| |
| if (ag->sco_conn != NULL) { |
| bt_conn_unref(ag->sco_conn); |
| ag->sco_conn = NULL; |
| } |
| |
| if (!call) { |
| return; |
| } |
| |
| call_state = call->call_state; |
| if ((call_state == BT_HFP_CALL_INCOMING) || (call_state == BT_HFP_CALL_OUTGOING)) { |
| bt_hfp_ag_call_reject(ag, call); |
| } |
| } |
| |
| static struct bt_conn *bt_hfp_ag_create_sco(struct bt_hfp_ag *ag) |
| { |
| static const struct bt_sco_chan_ops ops = { |
| .connected = hfp_ag_sco_connected, |
| .disconnected = hfp_ag_sco_disconnected, |
| }; |
| |
| LOG_DBG(""); |
| |
| if (ag->sco_conn == NULL) { |
| ag->sco_chan.ops = &ops; |
| |
| /* create SCO connection*/ |
| ag->sco_conn = bt_conn_create_sco(&ag->acl_conn->br.dst, &ag->sco_chan); |
| if (ag->sco_conn != NULL) { |
| LOG_DBG("Created sco %p", ag->sco_conn); |
| if (ag->sco_chan.sco == NULL) { |
| /* SCO connection exists */ |
| hfp_ag_sco_valid_call_update(ag); |
| } |
| } |
| } |
| |
| return ag->sco_conn; |
| } |
| |
| static int hfp_ag_open_sco(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call) |
| { |
| 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_conn == NULL) ? true : false; |
| if (create_sco) { |
| atomic_set_bit(ag->flags, BT_HFP_AG_CREATING_SCO); |
| } |
| hfp_ag_unlock(ag); |
| |
| if (create_sco) { |
| struct bt_conn *sco_conn = bt_hfp_ag_create_sco(ag); |
| |
| atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); |
| atomic_clear_bit(ag->flags, BT_HFP_AG_AUDIO_CONN); |
| |
| if (sco_conn == NULL) { |
| LOG_ERR("Fail to create sco connection!"); |
| return -ENOTCONN; |
| } |
| |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_AUDIO_CONN, call == NULL); |
| if (call) { |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_OPEN_SCO); |
| } |
| |
| LOG_DBG("SCO connection created (%p)", sco_conn); |
| } else { |
| if (call) { |
| if ((call->call_state == BT_HFP_CALL_INCOMING) || |
| atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); |
| bt_hfp_ag_call_ringing_cb(call, true); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int bt_hfp_ag_codec_select(struct bt_hfp_ag *ag) |
| { |
| int err; |
| |
| LOG_DBG(""); |
| |
| hfp_ag_lock(ag); |
| if (ag->selected_codec_id == 0) { |
| LOG_WRN("Codec is invalid, set default value"); |
| ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; |
| } |
| |
| if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) { |
| LOG_ERR("Codec is unsupported (codec id %d)", ag->selected_codec_id); |
| hfp_ag_unlock(ag); |
| return -EINVAL; |
| } |
| hfp_ag_unlock(ag); |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BCS:%d\r\n", ag->selected_codec_id); |
| if (err != 0) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| |
| static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call) |
| { |
| int err; |
| uint32_t hf_codec_ids; |
| |
| hfp_ag_lock(ag); |
| hf_codec_ids = ag->hf_codec_ids; |
| hfp_ag_unlock(ag); |
| |
| if ((hf_codec_ids != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED)) { |
| err = bt_hfp_ag_codec_select(ag); |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_CODEC_CONN, err == 0); |
| if (call) { |
| atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_OPEN_SCO, err == 0); |
| } |
| } else { |
| err = hfp_ag_open_sco(ag, call); |
| } |
| |
| return err; |
| } |
| |
| static void bt_hfp_ag_notify_ongoing_calls(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_ongoing_call *ongoing_call; |
| struct bt_hfp_ag_call *call; |
| int err; |
| int active_call_count; |
| int held_call_count; |
| bool sco_created = false; |
| uint8_t call_setup_value = BT_HFP_CALL_SETUP_NONE; |
| |
| for (size_t index = 0; index < ag->ongoing_call_count; index++) { |
| ongoing_call = &ag->ongoing_calls[index]; |
| |
| switch (ongoing_call->status) { |
| case BT_HFP_AG_CALL_STATUS_ACTIVE: |
| bt_hfp_ag_add_active_call(ag, ongoing_call); |
| break; |
| case BT_HFP_AG_CALL_STATUS_HELD: |
| bt_hfp_ag_add_held_call(ag, ongoing_call); |
| break; |
| case BT_HFP_AG_CALL_STATUS_DIALING: |
| case BT_HFP_AG_CALL_STATUS_ALERTING: |
| sco_created = true; |
| err = bt_hfp_ag_outgoing(ag, ongoing_call->number); |
| if (err != 0) { |
| sco_created = false; |
| LOG_ERR("Failed to initiate outgoing call: %d", err); |
| } else if (ongoing_call->status == BT_HFP_AG_CALL_STATUS_ALERTING) { |
| call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_IN_USING, |
| BT_HFP_CALL_OUTGOING); |
| if (call != NULL) { |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_ALERTING); |
| } |
| call_setup_value = BT_HFP_CALL_SETUP_REMOTE_ALERTING; |
| } else { |
| call_setup_value = BT_HFP_CALL_SETUP_OUTGOING; |
| } |
| break; |
| case BT_HFP_AG_CALL_STATUS_INCOMING: |
| case BT_HFP_AG_CALL_STATUS_WAITING: |
| sco_created = true; |
| err = bt_hfp_ag_remote_incoming(ag, ongoing_call->number); |
| if (err != 0) { |
| sco_created = false; |
| LOG_ERR("Failed to initiate remote incoming call: %d", err); |
| } else { |
| call_setup_value = BT_HFP_CALL_SETUP_INCOMING; |
| } |
| break; |
| case BT_HFP_AG_CALL_STATUS_INCOMING_HELD: |
| bt_hfp_ag_add_incoming_held_call(ag, ongoing_call); |
| break; |
| } |
| } |
| |
| call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_IN_USING, |
| BT_HFP_CALL_ACTIVE | BT_HFP_CALL_HOLD); |
| if ((sco_created == false) && (call != NULL)) { |
| err = bt_hfp_ag_create_audio_connection(ag, call); |
| if (err) { |
| LOG_ERR("Failed to create audio connection: %d", err); |
| } |
| } |
| |
| active_call_count = get_active_calls(ag); |
| held_call_count = get_held_calls(ag); |
| |
| if ((active_call_count > 0) && (held_call_count > 0)) { |
| ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = BT_HFP_CALL_HELD_ACTIVE_HELD; |
| } else if (held_call_count > 0) { |
| ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = BT_HFP_CALL_HELD_HELD; |
| } else { |
| ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = BT_HFP_CALL_HELD_NONE; |
| } |
| |
| if ((active_call_count > 0) || (held_call_count > 0) || |
| (get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD) != NULL)) { |
| ag->indicator_value[BT_HFP_AG_CALL_IND] = 1; |
| } |
| |
| ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = call_setup_value; |
| } |
| |
| static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag, void *user_data) |
| { |
| bool is_inband_ringtone; |
| |
| is_inband_ringtone = AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_INBAND_RINGTONE) ? true : false; |
| |
| if (is_inband_ringtone && !atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { |
| int err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR:1\r\n"); |
| |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, err == 0); |
| } |
| |
| (void)hfp_ag_next_step(ag, bt_hfp_ag_notify_ongoing_calls, NULL); |
| } |
| |
| static void bt_hfp_ag_slc_connected(struct bt_hfp_ag *ag, void *user_data) |
| { |
| bt_hfp_ag_set_state(ag, BT_HFP_CONNECTED); |
| (void)hfp_ag_next_step(ag, bt_hfp_ag_set_in_band_ring, NULL); |
| } |
| |
| static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| uint32_t number; |
| int err; |
| static const uint32_t command_line_prefix[] = {3, 0, 0}; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) { |
| err = get_number(buf, &number); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, ',')) { |
| return -ENOTSUP; |
| } |
| |
| if (command_line_prefix[i] != number) { |
| return -ENOTSUP; |
| } |
| } |
| |
| err = get_number(buf, &number); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (number == 1) { |
| atomic_set_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); |
| if (BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { |
| LOG_DBG("Waiting for AT+CHLD=?"); |
| return 0; |
| } |
| |
| err = hfp_ag_next_step(ag, bt_hfp_ag_slc_connected, NULL); |
| return err; |
| } else if (number == 0) { |
| atomic_clear_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); |
| } else { |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static void bt_hfp_ag_accept_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); |
| |
| if (bt_ag && bt_ag->accept) { |
| bt_ag->accept(call); |
| } |
| } |
| |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| static int chld_release_all(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| bt_hfp_call_state_t call_state; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| call_state = call->call_state; |
| if ((call_state != BT_HFP_CALL_ACTIVE) || |
| ((call_state == BT_HFP_CALL_ACTIVE) && |
| atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { |
| ag_terminate_call(call); |
| } |
| } |
| |
| ag_notify_call_held_ind(ag, NULL); |
| |
| return 0; |
| } |
| |
| static void bt_hfp_ag_accept_other_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| bt_hfp_call_state_t call_state; |
| int err; |
| |
| if (!call) { |
| return; |
| } |
| |
| call_state = call->call_state; |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); |
| |
| if (call_state == BT_HFP_CALL_HOLD) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, |
| BT_HFP_CALL_HELD_ACTIVE_HELD, NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| |
| if (call_state == BT_HFP_CALL_HOLD) { |
| if (bt_ag && bt_ag->retrieve) { |
| bt_ag->retrieve(call); |
| } |
| } else { |
| if (bt_ag && bt_ag->accept) { |
| bt_ag->accept(call); |
| } |
| } |
| |
| ag_notify_call_held_ind(ag, NULL); |
| } |
| |
| static void bt_hfp_ag_deactivate_accept_other_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| uint8_t call_setup; |
| |
| hfp_ag_lock(ag); |
| call_setup = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; |
| hfp_ag_unlock(ag); |
| |
| if (call_setup) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_accept_other_cb, user_data); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } else { |
| bt_hfp_ag_accept_other_cb(ag, user_data); |
| } |
| } |
| |
| static struct bt_hfp_ag_call *get_none_accept_calls(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| bt_hfp_call_state_t call_state; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| call_state = call->call_state; |
| if ((call_state == BT_HFP_CALL_OUTGOING) || (call_state == BT_HFP_CALL_INCOMING) || |
| (call_state == BT_HFP_CALL_ALERTING)) { |
| return call; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static struct bt_hfp_ag_call *get_none_active_calls(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| bt_hfp_call_state_t call_state; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| call_state = call->call_state; |
| if (call_state != BT_HFP_CALL_ACTIVE) { |
| return call; |
| } else if ((call_state == BT_HFP_CALL_ACTIVE) && |
| atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { |
| return call; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int chld_deactivate_calls_and_accept_other_call(struct bt_hfp_ag *ag, bool release) |
| { |
| struct bt_hfp_ag_call *call; |
| struct bt_hfp_ag_call *none_active_call; |
| |
| none_active_call = get_none_accept_calls(ag); |
| |
| if (!none_active_call) { |
| none_active_call = get_none_active_calls(ag); |
| } else { |
| if (!(atomic_test_bit(none_active_call->flags, BT_HFP_AG_CALL_INCOMING_3WAY) || |
| atomic_test_bit(none_active_call->flags, BT_HFP_AG_CALL_INCOMING))) { |
| return -ENOTSUP; |
| } |
| } |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if ((call->call_state == BT_HFP_CALL_ACTIVE) && |
| !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { |
| if (release) { |
| ag_terminate_call(call); |
| } else { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); |
| |
| if (bt_ag && bt_ag->held) { |
| bt_ag->held(call); |
| } |
| } |
| } |
| } |
| |
| return hfp_ag_next_step(ag, bt_hfp_ag_deactivate_accept_other_cb, none_active_call); |
| } |
| |
| static int chld_activate_held_call(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if (call->call_state == BT_HFP_CALL_HOLD) { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); |
| |
| if (bt_ag && bt_ag->retrieve) { |
| bt_ag->retrieve(call); |
| } |
| } |
| } |
| |
| ag_notify_call_held_ind(ag, NULL); |
| |
| return 0; |
| } |
| |
| static int chld_drop_conversation(struct bt_hfp_ag *ag) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if (call->call_state != BT_HFP_CALL_ACTIVE) { |
| return -ENOTSUP; |
| } |
| } |
| |
| if (!bt_ag || !bt_ag->explicit_call_transfer) { |
| return -ENOEXEC; |
| } |
| |
| bt_ag->explicit_call_transfer(ag); |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); |
| } |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { |
| LOG_DBG("Close SCO connection"); |
| hfp_ag_close_sco(ag); |
| } |
| |
| ag_notify_call_held_ind(ag, NULL); |
| |
| return 0; |
| } |
| |
| static int chld_release_call(struct bt_hfp_ag *ag, uint8_t call_index) |
| { |
| struct bt_hfp_ag_call *call; |
| bt_hfp_call_state_t call_state; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if (index != call_index) { |
| continue; |
| } |
| |
| call_state = call->call_state; |
| if ((call_state == BT_HFP_CALL_HOLD) || |
| ((call_state == BT_HFP_CALL_ACTIVE) && |
| !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { |
| ag_terminate_call(call); |
| } else { |
| ag_reject_call(call); |
| } |
| } |
| |
| ag_notify_call_held_ind(ag, NULL); |
| |
| return 0; |
| } |
| |
| static int chld_held_other_calls(struct bt_hfp_ag *ag, uint8_t call_index) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if (index == call_index) { |
| continue; |
| } |
| |
| if ((call->call_state == BT_HFP_CALL_ACTIVE) && |
| !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); |
| |
| if (bt_ag && bt_ag->held) { |
| bt_ag->held(call); |
| } |
| } |
| } |
| |
| call = &ag->calls[call_index]; |
| if (call->call_state == BT_HFP_CALL_HOLD) { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); |
| if (bt_ag && bt_ag->retrieve) { |
| bt_ag->retrieve(call); |
| } |
| } |
| |
| ag_notify_call_held_ind(ag, NULL); |
| |
| return 0; |
| } |
| |
| static int chld_other(struct bt_hfp_ag *ag, uint32_t value) |
| { |
| uint8_t index; |
| uint8_t command; |
| |
| index = value % 10; |
| command = value / 10; |
| |
| if (!index) { |
| return -ENOEXEC; |
| } |
| |
| index = index - 1; |
| |
| if (index >= ARRAY_SIZE(ag->calls)) { |
| return -ENOEXEC; |
| } |
| |
| switch (command) { |
| case BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER: |
| return chld_release_call(ag, index); |
| case BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER: |
| return chld_held_other_calls(ag, index); |
| } |
| |
| return -ENOEXEC; |
| } |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| |
| static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| uint32_t value; |
| int err; |
| char *response; |
| |
| if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { |
| return -ENOEXEC; |
| } |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| if (is_char(buf, '?')) { |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| #if defined(CONFIG_BT_HFP_AG_ECC) |
| response = "+CHLD:(0,1,1x,2,2x,3,4)"; |
| #else |
| response = "+CHLD:(0,1,2,3,4)"; |
| #endif /* CONFIG_BT_HFP_AG_ECC */ |
| err = hfp_ag_send_data(ag, bt_hfp_ag_slc_connected, NULL, "\r\n%s\r\n", response); |
| return err; |
| } |
| |
| err = get_number(buf, &value); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| switch (value) { |
| case BT_HFP_CHLD_RELEASE_ALL: |
| return chld_release_all(ag); |
| case BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER: |
| return chld_deactivate_calls_and_accept_other_call(ag, true); |
| case BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER: |
| return chld_deactivate_calls_and_accept_other_call(ag, false); |
| case BT_HFP_CALL_ACTIVE_HELD: |
| return chld_activate_held_call(ag); |
| case BT_HFP_CALL_QUITE: |
| return chld_drop_conversation(ag); |
| } |
| |
| return chld_other(ag, value); |
| #else |
| return -ENOEXEC; |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| } |
| |
| static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| uint32_t indicator; |
| uint32_t hf_indicators = 0U; |
| uint32_t supported_indicators = 0U; |
| int err; |
| char *data; |
| uint32_t len; |
| |
| if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_HF_IND, BT_HFP_AG_FEATURE_HF_IND)) { |
| return -ENOEXEC; |
| } |
| |
| if (is_char(buf, '?')) { |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| hf_indicators = ag->hf_indicators; |
| supported_indicators = ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; |
| len = MIN(NUM_BITS(sizeof(supported_indicators)), HFP_HF_IND_MAX); |
| for (int i = 1; i < len; i++) { |
| bool enabled; |
| |
| if (!(BIT(i) & supported_indicators)) { |
| continue; |
| } |
| |
| enabled = BIT(i) & hf_indicators; |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", i, |
| enabled ? 1 : 0); |
| if (err) { |
| return err; |
| } |
| |
| supported_indicators &= ~BIT(i); |
| |
| if (!supported_indicators) { |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| if (is_char(buf, '?')) { |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| data = &ag->buffer[0]; |
| *data = '('; |
| data++; |
| hf_indicators = ag->hf_indicators_of_ag; |
| len = MIN(NUM_BITS(sizeof(hf_indicators)), HFP_HF_IND_MAX); |
| for (int i = 1; (i < len) && (hf_indicators != 0); i++) { |
| if (BIT(i) & hf_indicators) { |
| int length = snprintk( |
| data, (char *)&ag->buffer[HF_MAX_BUF_LEN - 1] - data - 2, |
| "%d", i); |
| data += length; |
| hf_indicators &= ~BIT(i); |
| } |
| if (hf_indicators != 0) { |
| *data = ','; |
| data++; |
| } |
| } |
| *data = ')'; |
| data++; |
| *data = '\0'; |
| data++; |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%s\r\n", &ag->buffer[0]); |
| return err; |
| } |
| |
| while (buf->len > 0) { |
| err = get_number(buf, &indicator); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, ',')) { |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| } |
| |
| if (indicator < NUM_BITS(sizeof(hf_indicators))) { |
| hf_indicators |= BIT(indicator); |
| } |
| } |
| |
| ag->hf_indicators_of_hf = hf_indicators; |
| |
| return 0; |
| } |
| |
| static int bt_hfp_ag_cmee_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| uint32_t cmee; |
| int err; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &cmee); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (cmee > 1) { |
| return -ENOTSUP; |
| } |
| |
| if (BT_HFP_AG_FEATURE_EXT_ERR_ENABLE) { |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_CMEE_ENABLE, cmee == 1); |
| return 0; |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static void bt_hfp_ag_terminate_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| if (call) { |
| ag_terminate_call(call); |
| } |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) { |
| LOG_DBG("It is not audio connection"); |
| hfp_ag_close_sco(ag); |
| } |
| } |
| |
| static void bt_hfp_ag_unit_call_terminate(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_terminate_cb, |
| user_data); |
| if (err != 0) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| |
| static void bt_hfp_ag_active_terminate_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| bt_hfp_ag_unit_call_terminate(ag, user_data); |
| } |
| |
| static void bt_hfp_ag_call_terminate(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_active_terminate_cb, user_data); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| |
| static int bt_hfp_ag_chup_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| bt_hfp_call_state_t call_state; |
| struct bt_hfp_ag_call *call; |
| int call_count; |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| call_count = get_none_released_calls(ag); |
| |
| if (call_count == 1) { |
| bt_hfp_ag_tx_cb_t next_step = NULL; |
| |
| call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING); |
| if (!call) { |
| return 0; |
| } |
| |
| call_state = call->call_state; |
| if (call_state == BT_HFP_CALL_ALERTING) { |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| next_step = bt_hfp_ag_call_terminate; |
| } else { |
| next_step = bt_hfp_ag_call_reject; |
| } |
| } else if (call_state == BT_HFP_CALL_ACTIVE) { |
| next_step = bt_hfp_ag_unit_call_terminate; |
| } |
| |
| if (next_step) { |
| err = hfp_ag_next_step(ag, next_step, call); |
| return err; |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if (call->call_state == BT_HFP_CALL_ACTIVE) { |
| err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, call); |
| } |
| } |
| return 0; |
| } |
| |
| static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag_call *call) |
| { |
| uint8_t status = 0; |
| struct bt_hfp_ag *ag = call->ag; |
| |
| hfp_ag_lock(ag); |
| switch (call->call_state) { |
| case BT_HFP_CALL_TERMINATE: |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| status = BT_HFP_CLCC_STATUS_INCOMING; |
| } else { |
| status = BT_HFP_CLCC_STATUS_DIALING; |
| } |
| break; |
| case BT_HFP_CALL_OUTGOING: |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) { |
| status = BT_HFP_CLCC_STATUS_ALERTING; |
| } else { |
| status = BT_HFP_CLCC_STATUS_DIALING; |
| } |
| break; |
| case BT_HFP_CALL_INCOMING: |
| status = BT_HFP_CLCC_STATUS_INCOMING; |
| break; |
| case BT_HFP_CALL_ALERTING: |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| status = BT_HFP_CLCC_STATUS_WAITING; |
| } else { |
| status = BT_HFP_CLCC_STATUS_ALERTING; |
| } |
| break; |
| case BT_HFP_CALL_ACTIVE: |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { |
| status = BT_HFP_CLCC_STATUS_CALL_HELD_HOLD; |
| } else { |
| status = BT_HFP_CLCC_STATUS_ACTIVE; |
| } |
| break; |
| case BT_HFP_CALL_HOLD: |
| status = BT_HFP_CLCC_STATUS_HELD; |
| break; |
| default: |
| break; |
| } |
| hfp_ag_unlock(ag); |
| |
| return status; |
| } |
| |
| static int bt_hfp_ag_clcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| struct bt_hfp_ag_call *call; |
| uint8_t dir; |
| uint8_t status; |
| uint8_t mode; |
| uint8_t mpty; |
| int active_calls; |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| active_calls = get_active_calls(ag); |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| dir = atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) ? |
| BT_HFP_CLCC_DIR_INCOMING : BT_HFP_CLCC_DIR_OUTGOING; |
| status = bt_hfp_get_call_state(call); |
| mode = 0; |
| mpty = (status == BT_HFP_CLCC_STATUS_ACTIVE) && (active_calls > 1) ? 1 : 0; |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLCC:%d,%d,%d,%d,%d,\"%s\",%d\r\n", |
| index + 1, dir, status, mode, mpty, call->number, |
| call->type); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| |
| /* AG shall always send OK response to HF */ |
| return 0; |
| } |
| |
| static int bt_hfp_ag_bia_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| uint32_t number; |
| int err; |
| int index = 0; |
| uint32_t indicator; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| hfp_ag_lock(ag); |
| indicator = ag->indicator; |
| hfp_ag_unlock(ag); |
| |
| while (buf->len > 0) { |
| err = get_number(buf, &number); |
| if (err == 0) { |
| /* Valid number */ |
| if (number) { |
| indicator |= BIT(index); |
| } else { |
| indicator &= ~BIT(index); |
| } |
| } |
| |
| if (is_char(buf, ',')) { |
| index++; |
| } else { |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| if (buf->len != 0) { |
| return -ENOTSUP; |
| } |
| } |
| } |
| |
| /* Force call, call setup and held call indicators are enabled. */ |
| indicator |= BIT(BT_HFP_AG_CALL_IND) | BIT(BT_HFP_AG_CALL_SETUP_IND) | |
| BIT(BT_HFP_AG_CALL_HELD_IND); |
| |
| hfp_ag_lock(ag); |
| ag->indicator = indicator; |
| hfp_ag_unlock(ag); |
| |
| return 0; |
| } |
| |
| static void bt_hfp_ag_audio_connection(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| err = bt_hfp_ag_create_audio_connection(ag, call); |
| if (err) { |
| bt_hfp_ag_unit_call_terminate(ag, user_data); |
| } |
| } |
| |
| static void bt_hfp_ag_call_setup_none_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, |
| BT_HFP_CALL_HELD_ACTIVE_HELD, bt_hfp_ag_accept_cb, |
| call); |
| if (err) { |
| bt_hfp_ag_unit_call_terminate(ag, user_data); |
| } |
| return; |
| } |
| |
| bt_hfp_ag_audio_connection(ag, user_data); |
| } |
| |
| static void bt_hfp_ag_unit_call_accept(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, user_data); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_call_setup_none_cb, user_data); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| |
| static int bt_hfp_ag_ata_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| int call_count; |
| struct bt_hfp_ag_call *call; |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| hfp_ag_lock(ag); |
| call_count = get_none_released_calls(ag); |
| if (call_count != 1) { |
| hfp_ag_unlock(ag); |
| return -ENOTSUP; |
| } |
| |
| call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING); |
| __ASSERT(call, "Invalid call object"); |
| |
| if (call->call_state != BT_HFP_CALL_ALERTING) { |
| hfp_ag_unlock(ag); |
| return -ENOTSUP; |
| } |
| hfp_ag_unlock(ag); |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| return -ENOTSUP; |
| } |
| |
| err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_accept, call); |
| |
| return err; |
| } |
| |
| static int bt_hfp_ag_cops_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t number; |
| |
| if (is_char(buf, '=')) { |
| static const uint32_t command_line_prefix[] = {3, 0}; |
| |
| for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) { |
| err = get_number(buf, &number); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (command_line_prefix[i] != number) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, ',')) { |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| } |
| } |
| |
| atomic_set_bit(ag->flags, BT_HFP_AG_COPS_SET); |
| return 0; |
| } |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_COPS_SET)) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '?')) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+COPS:%d,%d,\"%s\"\r\n", ag->mode, 0, |
| ag->operator); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| static int bt_hfp_ag_bcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| #if BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE |
| int err; |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->selected_codec_id && (!(ag->hf_codec_ids & BIT(ag->selected_codec_id)))) { |
| hfp_ag_unlock(ag); |
| return -ENOTSUP; |
| } |
| |
| if (ag->sco_conn != NULL) { |
| hfp_ag_unlock(ag); |
| return -ECONNREFUSED; |
| } |
| hfp_ag_unlock(ag); |
| |
| if (bt_ag && bt_ag->audio_connect_req) { |
| bt_ag->audio_connect_req(ag); |
| return 0; |
| } |
| |
| err = hfp_ag_next_step(ag, bt_hfp_ag_audio_connection, NULL); |
| |
| return err; |
| #else |
| return -ENOTSUP; |
| #endif /* BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE */ |
| } |
| |
| static void bt_hfp_ag_unit_codec_conn_setup(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err = hfp_ag_open_sco(ag, user_data); |
| |
| if (err) { |
| bt_hfp_ag_call_reject(ag, user_data); |
| } |
| } |
| |
| static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t number; |
| bool codec_conn; |
| struct bt_hfp_ag_call *call; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &number); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { |
| return -ESRCH; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->selected_codec_id != number) { |
| LOG_ERR("Received codec id %d is not aligned with selected %d", number, |
| ag->selected_codec_id); |
| err = -ENOTSUP; |
| } else if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) { |
| LOG_ERR("Selected codec id %d is unsupported %d", ag->selected_codec_id, |
| ag->hf_codec_ids); |
| err = -ENOTSUP; |
| } |
| hfp_ag_unlock(ag); |
| |
| codec_conn = atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN); |
| atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); |
| |
| call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO); |
| |
| if (err == 0) { |
| if (codec_conn && bt_ag && bt_ag->codec_negotiate) { |
| bt_ag->codec_negotiate(ag, err); |
| } |
| err = hfp_ag_next_step(ag, bt_hfp_ag_unit_codec_conn_setup, call); |
| } else { |
| if (codec_conn && bt_ag && bt_ag->codec_negotiate) { |
| bt_ag->codec_negotiate(ag, err); |
| } |
| |
| if (call) { |
| if (call->call_state != BT_HFP_CALL_TERMINATE) { |
| (void)hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, call); |
| } |
| } |
| } |
| |
| return err; |
| } |
| |
| static void bt_hfp_ag_outgoing_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_OUTGOING); |
| |
| if (bt_ag && bt_ag->outgoing) { |
| bt_ag->outgoing(ag, call, call->number); |
| } |
| |
| if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING) || |
| atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { |
| int err; |
| |
| err = bt_hfp_ag_create_audio_connection(ag, call); |
| if (err) { |
| bt_hfp_ag_call_reject(ag, user_data); |
| } |
| } else if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); |
| bt_hfp_ag_call_ringing_cb(call, false); |
| } |
| } |
| |
| static void bt_hfp_ag_unit_call_outgoing(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| int err; |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_OUTGOING, |
| bt_hfp_ag_outgoing_cb, call); |
| |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| static void bt_hfp_ag_call_held_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| if ((call->call_state == BT_HFP_CALL_ACTIVE) && |
| !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); |
| if (bt_ag && bt_ag->held) { |
| bt_ag->held(call); |
| } |
| } |
| } |
| |
| bt_hfp_ag_unit_call_outgoing(ag, user_data); |
| } |
| |
| static void bt_hfp_ag_unit_call_outgoing_3way(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| int err; |
| uint8_t old_value; |
| |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY); |
| |
| hfp_ag_lock(ag); |
| old_value = ag->indicator_value[BT_HFP_AG_CALL_HELD_IND]; |
| hfp_ag_unlock(ag); |
| if (old_value == BT_HFP_CALL_HELD_HELD) { |
| LOG_WRN("No active call"); |
| bt_hfp_ag_call_held_cb(ag, user_data); |
| return; |
| } |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, BT_HFP_CALL_HELD_HELD, |
| bt_hfp_ag_call_held_cb, call); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| |
| static int bt_hfp_ag_outgoing_call(struct bt_hfp_ag *ag, const char *number, uint8_t type) |
| { |
| int err; |
| size_t len; |
| struct bt_hfp_ag_call *call; |
| int call_count; |
| |
| len = strlen(number); |
| if (len == 0) { |
| return -ENOTSUP; |
| } |
| |
| if (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) { |
| return -ENAMETOOLONG; |
| } |
| |
| hfp_ag_lock(ag); |
| (void)strcpy(ag->last_number, number); |
| ag->type = type; |
| hfp_ag_unlock(ag); |
| |
| call = get_call_from_number(ag, number, type); |
| if (call) { |
| return -EBUSY; |
| } |
| |
| call_count = get_none_released_calls(ag); |
| |
| if (call_count) { |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, |
| BT_HFP_AG_FEATURE_3WAY_CALL)) { |
| return -ENOEXEC; |
| } |
| |
| if (!get_active_held_calls(ag)) { |
| LOG_WRN("The first call is not accepted"); |
| return -EBUSY; |
| } |
| #else |
| return -EBUSY; |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| } |
| |
| call = get_new_call(ag, number, type); |
| if (!call) { |
| LOG_WRN("Cannot allocate a new call object"); |
| return -ENOMEM; |
| } |
| |
| atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING); |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_OUTGOING); |
| |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| if (call_count) { |
| err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing_3way, call); |
| if (err) { |
| free_call(call); |
| } |
| return err; |
| } |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| |
| err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, call); |
| if (err) { |
| free_call(call); |
| } |
| return err; |
| } |
| |
| static int bt_hfp_ag_atd_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| char *number = NULL; |
| bool is_memory_dial = false; |
| |
| if (buf->data[buf->len - 1] != '\r') { |
| return -ENOTSUP; |
| } |
| |
| if (is_char(buf, '>')) { |
| is_memory_dial = true; |
| } |
| |
| if ((buf->len - 1) > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) { |
| return -ENAMETOOLONG; |
| } |
| |
| buf->data[buf->len - 1] = '\0'; |
| |
| if (is_memory_dial) { |
| if (bt_ag && bt_ag->memory_dial) { |
| err = bt_ag->memory_dial(ag, &buf->data[0], &number); |
| if ((err != 0) || (number == NULL)) { |
| return -ENOTSUP; |
| } |
| } else { |
| return -ENOTSUP; |
| } |
| } else { |
| number = &buf->data[0]; |
| if (bt_ag && bt_ag->number_call) { |
| err = bt_ag->number_call(ag, &buf->data[0]); |
| if (err) { |
| return err; |
| } |
| } else { |
| return -ENOTSUP; |
| } |
| } |
| |
| return bt_hfp_ag_outgoing_call(ag, number, 0); |
| } |
| |
| static int bt_hfp_ag_bldn_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| return bt_hfp_ag_outgoing_call(ag, ag->last_number, ag->type); |
| } |
| |
| static int bt_hfp_ag_clip_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t clip; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &clip); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (clip > 1) { |
| return -ENOTSUP; |
| } |
| |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_CLIP_ENABLE, clip == 1); |
| |
| return err; |
| } |
| |
| static int bt_hfp_ag_vgm_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t vgm; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &vgm); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (vgm > BT_HFP_HF_VGM_GAIN_MAX) { |
| LOG_ERR("Invalid vgm (%d>%d)", vgm, BT_HFP_HF_VGM_GAIN_MAX); |
| return -ENOTSUP; |
| } |
| |
| if (bt_ag && bt_ag->vgm) { |
| bt_ag->vgm(ag, (uint8_t)vgm); |
| } |
| return err; |
| } |
| |
| static int bt_hfp_ag_vgs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t vgs; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &vgs); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (vgs > BT_HFP_HF_VGS_GAIN_MAX) { |
| LOG_ERR("Invalid vgs (%d>%d)", vgs, BT_HFP_HF_VGS_GAIN_MAX); |
| return -ENOTSUP; |
| } |
| |
| if (bt_ag && bt_ag->vgs) { |
| bt_ag->vgs(ag, (uint8_t)vgs); |
| } |
| return err; |
| } |
| |
| static int bt_hfp_ag_nrec_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t disable; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &disable); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_ECNR)) { |
| return -ENOTSUP; |
| } |
| |
| if (disable) { |
| LOG_ERR("Only support EC NR disable"); |
| return -ENOTSUP; |
| } |
| |
| #if defined(CONFIG_BT_HFP_AG_ECNR) |
| if (bt_ag && bt_ag->ecnr_turn_off) { |
| bt_ag->ecnr_turn_off(ag); |
| return 0; |
| } |
| #endif /* CONFIG_BT_HFP_AG_ECNR */ |
| return -ENOTSUP; |
| } |
| |
| static void btrh_held_call_updated_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); |
| } |
| |
| static void btrh_held_call_setup_updated_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| if (bt_ag && bt_ag->incoming_held) { |
| bt_ag->incoming_held(call); |
| } |
| } |
| |
| static void btrh_held_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, btrh_held_call_updated_cb, |
| user_data); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| bt_hfp_ag_unit_call_terminate(ag, user_data); |
| } |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| btrh_held_call_setup_updated_cb, user_data); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| bt_hfp_ag_unit_call_terminate(ag, user_data); |
| } |
| } |
| |
| static void btrh_accept_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| if (bt_ag && bt_ag->accept) { |
| bt_ag->accept(call); |
| } |
| |
| err = bt_hfp_ag_create_audio_connection(ag, call); |
| if (err) { |
| bt_hfp_ag_unit_call_terminate(ag, user_data); |
| } |
| } |
| |
| static void btrh_reject_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, bt_hfp_ag_reject_cb, user_data); |
| if (err) { |
| bt_hfp_ag_unit_call_terminate(ag, user_data); |
| } |
| } |
| |
| static int bt_hfp_ag_btrh_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t action; |
| struct bt_hfp_ag_call *call; |
| |
| if (is_char(buf, '?')) { |
| call = get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD); |
| if (call && (call->call_state == BT_HFP_CALL_ACTIVE)) { |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BTRH:%d\r\n", |
| BT_HFP_BTRH_ON_HOLD); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| return 0; |
| } |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &action); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (action == BT_HFP_BTRH_ON_HOLD) { |
| call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING, |
| BT_HFP_CALL_ALERTING | BT_HFP_CALL_INCOMING); |
| if (call) { |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); |
| err = hfp_ag_send_data(ag, btrh_held_cb, call, "\r\n+BTRH:%d\r\n", |
| BT_HFP_BTRH_ON_HOLD); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| } else if (action == BT_HFP_BTRH_ACCEPTED) { |
| call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING_HELD, |
| BT_HFP_CALL_ACTIVE); |
| if (call) { |
| atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); |
| err = hfp_ag_send_data(ag, btrh_accept_cb, call, "\r\n+BTRH:%d\r\n", |
| BT_HFP_BTRH_ACCEPTED); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| } else if (action == BT_HFP_BTRH_REJECTED) { |
| call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING_HELD, |
| BT_HFP_CALL_ACTIVE); |
| if (call) { |
| atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); |
| err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", |
| BT_HFP_BTRH_REJECTED); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| } |
| |
| return -ENOTSUP; |
| } |
| |
| static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t value; |
| |
| if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, BT_HFP_AG_FEATURE_3WAY_CALL)) { |
| return -ENOEXEC; |
| } |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &value); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (value > 1) { |
| return -ENOTSUP; |
| } |
| |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_CCWA_ENABLE, value); |
| return 0; |
| } |
| |
| static void bt_hfp_ag_vr_activate(struct bt_hfp_ag *ag, void *user_data) |
| { |
| bool feature; |
| |
| feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, |
| BT_HFP_AG_FEATURE_ENH_VOICE_RECG); |
| |
| #if defined(CONFIG_BT_HFP_AG_VOICE_RECG) |
| if (bt_ag && bt_ag->voice_recognition) { |
| bt_ag->voice_recognition(ag, true); |
| } else { |
| (void)bt_hfp_ag_audio_connect(ag, BT_HFP_AG_CODEC_CVSD); |
| } |
| #endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ |
| |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_VRE_R2A, !feature); |
| |
| #if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) |
| if (atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { |
| if (bt_ag && bt_ag->ready_to_accept_audio) { |
| bt_ag->ready_to_accept_audio(ag); |
| } |
| } |
| #endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ |
| } |
| |
| static void bt_hfp_ag_vr_deactivate(struct bt_hfp_ag *ag, void *user_data) |
| { |
| #if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) |
| if (bt_ag && bt_ag->voice_recognition) { |
| bt_ag->voice_recognition(ag, false); |
| } |
| #endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ |
| } |
| |
| static void bt_hfp_ag_vr_ready2accept(struct bt_hfp_ag *ag, void *user_data) |
| { |
| atomic_set_bit(ag->flags, BT_HFP_AG_VRE_R2A); |
| |
| #if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) |
| if (bt_ag && bt_ag->ready_to_accept_audio) { |
| bt_ag->ready_to_accept_audio(ag); |
| } |
| #endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ |
| } |
| |
| static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t value; |
| |
| if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOICE_RECG, BT_HFP_AG_FEATURE_VOICE_RECG)) { |
| return -ENOEXEC; |
| } |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &value); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| switch (value) { |
| case BT_HFP_BVRA_DEACTIVATION: |
| if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { |
| LOG_WRN("VR is not activated"); |
| return -ENOTSUP; |
| } |
| err = hfp_ag_next_step(ag, bt_hfp_ag_vr_deactivate, NULL); |
| break; |
| case BT_HFP_BVRA_ACTIVATION: |
| if (atomic_test_and_set_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { |
| LOG_WRN("VR has been activated"); |
| return -ENOTSUP; |
| } |
| atomic_clear_bit(ag->flags, BT_HFP_AG_VRE_R2A); |
| err = hfp_ag_next_step(ag, bt_hfp_ag_vr_activate, NULL); |
| break; |
| case BT_HFP_BVRA_READY_TO_ACCEPT: |
| if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, |
| BT_HFP_AG_FEATURE_ENH_VOICE_RECG)) { |
| LOG_WRN("Enhance voice recognition is not supported"); |
| return -ENOEXEC; |
| } |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { |
| LOG_WRN("Voice recognition is not activated"); |
| return -ENOTSUP; |
| } |
| err = hfp_ag_next_step(ag, bt_hfp_ag_vr_ready2accept, NULL); |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return err; |
| } |
| |
| static int bt_hfp_ag_binp_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| uint32_t value; |
| #if defined(CONFIG_BT_HFP_AG_VOICE_TAG) |
| char *number = NULL; |
| #endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ |
| |
| if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_VOICE_TAG)) { |
| return -ENOEXEC; |
| } |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_number(buf, &value); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (value != 1) { |
| return -ENOTSUP; |
| } |
| |
| #if defined(CONFIG_BT_HFP_AG_VOICE_TAG) |
| if (bt_ag && bt_ag->request_phone_number) { |
| err = bt_ag->request_phone_number(ag, &number); |
| if (err) { |
| LOG_DBG("Cannot request phone number :(%d)", err); |
| return err; |
| } |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BINP:\"%s\"\r\n", number); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| #endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ |
| |
| return -ENOTSUP; |
| } |
| |
| static int bt_hfp_ag_vts_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| char code = 0; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| err = get_char(buf, &code); |
| if (err != 0) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (!IS_VALID_DTMF(code)) { |
| LOG_ERR("Invalid code"); |
| return -EINVAL; |
| } |
| |
| if (!get_active_calls(ag)) { |
| LOG_ERR("Not valid ongoing call"); |
| return -ENOTSUP; |
| } |
| |
| if (bt_ag && bt_ag->transmit_dtmf_code) { |
| bt_ag->transmit_dtmf_code(ag, code); |
| } else { |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static int send_subscriber_number(struct bt_hfp_ag *ag, char *number, |
| uint8_t type, uint8_t service) |
| { |
| int err; |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CNUM:,\"%s\",%d,,%d\r\n", |
| number, type, service); |
| if (err) { |
| LOG_ERR("Fail to send subscriber number :(%d)", err); |
| } |
| return err; |
| } |
| |
| static int bt_hfp_ag_cnum_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| int err; |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| if (bt_ag && bt_ag->subscriber_number) { |
| err = bt_ag->subscriber_number(ag, send_subscriber_number); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int bt_hfp_ag_biev_handler(struct bt_hfp_ag *ag, struct net_buf *buf) |
| { |
| uint32_t indicator; |
| uint32_t value; |
| |
| if (!is_char(buf, '=')) { |
| return -ENOTSUP; |
| } |
| |
| if (get_number(buf, &indicator)) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, ',')) { |
| return -ENOTSUP; |
| } |
| |
| if (get_number(buf, &value)) { |
| return -ENOTSUP; |
| } |
| |
| if (!is_char(buf, '\r')) { |
| return -ENOTSUP; |
| } |
| |
| hfp_ag_lock(ag); |
| if (!(ag->hf_indicators_of_ag & BIT(indicator))) { |
| hfp_ag_unlock(ag); |
| return -ENOTSUP; |
| } |
| hfp_ag_unlock(ag); |
| |
| #if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) |
| if (bt_ag && bt_ag->hf_indicator_value) { |
| bt_ag->hf_indicator_value(ag, indicator, value); |
| } |
| #endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ |
| |
| return 0; |
| } |
| |
| static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = { |
| {"AT+BRSF", bt_hfp_ag_brsf_handler}, {"AT+BAC", bt_hfp_ag_bac_handler}, |
| {"AT+CIND", bt_hfp_ag_cind_handler}, {"AT+CMER", bt_hfp_ag_cmer_handler}, |
| {"AT+CHLD", bt_hfp_ag_chld_handler}, {"AT+BIND", bt_hfp_ag_bind_handler}, |
| {"AT+CMEE", bt_hfp_ag_cmee_handler}, {"AT+CHUP", bt_hfp_ag_chup_handler}, |
| {"AT+CLCC", bt_hfp_ag_clcc_handler}, {"AT+BIA", bt_hfp_ag_bia_handler}, |
| {"ATA", bt_hfp_ag_ata_handler}, {"AT+COPS", bt_hfp_ag_cops_handler}, |
| {"AT+BCC", bt_hfp_ag_bcc_handler}, {"AT+BCS", bt_hfp_ag_bcs_handler}, |
| {"ATD", bt_hfp_ag_atd_handler}, {"AT+BLDN", bt_hfp_ag_bldn_handler}, |
| {"AT+CLIP", bt_hfp_ag_clip_handler}, {"AT+VGM", bt_hfp_ag_vgm_handler}, |
| {"AT+VGS", bt_hfp_ag_vgs_handler}, {"AT+NREC", bt_hfp_ag_nrec_handler}, |
| {"AT+BTRH", bt_hfp_ag_btrh_handler}, {"AT+CCWA", bt_hfp_ag_ccwa_handler}, |
| {"AT+BVRA", bt_hfp_ag_bvra_handler}, {"AT+BINP", bt_hfp_ag_binp_handler}, |
| {"AT+VTS", bt_hfp_ag_vts_handler}, {"AT+CNUM", bt_hfp_ag_cnum_handler}, |
| {"AT+BIEV", bt_hfp_ag_biev_handler}, |
| }; |
| |
| static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) |
| { |
| 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); |
| sys_snode_t *node; |
| struct bt_ag_tx *tx; |
| struct bt_hfp_ag_call *call; |
| |
| k_work_cancel_delayable(&ag->tx_work); |
| |
| hfp_ag_lock(ag); |
| 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); |
| |
| for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) { |
| call = &ag->calls[index]; |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| continue; |
| } |
| |
| release_call(call); |
| } |
| |
| hfp_ag_close_sco(ag); |
| |
| ag->acl_conn = 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 = -ENOEXEC; |
| |
| 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 == -EINPROGRESS) { |
| LOG_DBG("OK code will be replied later"); |
| return; |
| } |
| |
| 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) { |
| state = ag->state; |
| 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_DBG("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 const char *bt_ag_get_call_state_string(bt_hfp_call_state_t call_state) |
| { |
| const char *s; |
| |
| switch (call_state) { |
| case BT_HFP_CALL_TERMINATE: |
| s = "terminate"; |
| break; |
| case BT_HFP_CALL_ACTIVE: |
| s = "active"; |
| break; |
| case BT_HFP_CALL_HOLD: |
| s = "hold"; |
| break; |
| case BT_HFP_CALL_OUTGOING: |
| s = "outgoing"; |
| break; |
| case BT_HFP_CALL_INCOMING: |
| s = "incoming"; |
| break; |
| case BT_HFP_CALL_ALERTING: |
| s = "alerting"; |
| break; |
| default: |
| s = "unknown"; |
| break; |
| } |
| |
| return s; |
| } |
| |
| static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| bt_hfp_call_state_t call_state; |
| uint8_t call_setup_ind; |
| uint8_t call_ind; |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| LOG_DBG(""); |
| |
| hfp_ag_lock(ag); |
| call_state = call->call_state; |
| call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; |
| call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; |
| hfp_ag_unlock(ag); |
| |
| switch (call_state) { |
| case BT_HFP_CALL_TERMINATE: |
| break; |
| case BT_HFP_CALL_ACTIVE: |
| break; |
| case BT_HFP_CALL_HOLD: |
| break; |
| case BT_HFP_CALL_OUTGOING: |
| case BT_HFP_CALL_INCOMING: |
| case BT_HFP_CALL_ALERTING: |
| default: |
| LOG_WRN("Call timeout, status %s", bt_ag_get_call_state_string(call_state)); |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY) || |
| atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { |
| if (!call_setup_ind) { |
| return; |
| } |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, |
| BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_reject_cb, call); |
| if (err) { |
| LOG_ERR("Fail to send indicator"); |
| bt_hfp_ag_terminate_cb(ag, call); |
| } |
| return; |
| } |
| |
| if (call_setup_ind && call_ind) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, |
| BT_HFP_CALL_SETUP_NONE, NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to send indicator"); |
| bt_hfp_ag_terminate_cb(ag, call); |
| } else { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, |
| bt_hfp_ag_terminate_cb, call); |
| if (err) { |
| LOG_ERR("Fail to send indicator"); |
| bt_hfp_ag_terminate_cb(ag, call); |
| } |
| } |
| } else if (call_setup_ind) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, |
| BT_HFP_CALL_SETUP_NONE, bt_hfp_ag_reject_cb, |
| call); |
| if (err) { |
| LOG_ERR("Fail to send indicator"); |
| bt_hfp_ag_terminate_cb(ag, call); |
| } |
| } else if (call_ind) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, |
| bt_hfp_ag_terminate_cb, call); |
| if (err) { |
| LOG_ERR("Fail to send indicator"); |
| bt_hfp_ag_terminate_cb(ag, call); |
| } |
| } |
| break; |
| } |
| } |
| |
| static void bt_ag_deferred_work(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct bt_hfp_ag_call *call = CONTAINER_OF(dwork, struct bt_hfp_ag_call, deferred_work); |
| struct bt_hfp_ag *ag = call->ag; |
| |
| LOG_DBG(""); |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| return; |
| } |
| |
| (void)hfp_ag_next_step(ag, bt_ag_deferred_work_cb, call); |
| } |
| |
| static void bt_ag_ringing_work_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| LOG_DBG(""); |
| |
| if (call->call_state == BT_HFP_CALL_ALERTING) { |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| return; |
| } |
| |
| k_work_reschedule(&call->ringing_work, |
| K_SECONDS(CONFIG_BT_HFP_AG_RING_NOTIFY_INTERVAL)); |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\nRING\r\n"); |
| if (err) { |
| LOG_ERR("Fail to send RING %d", err); |
| } else { |
| if (atomic_test_bit(ag->flags, BT_HFP_AG_CLIP_ENABLE)) { |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CLIP:\"%s\",%d\r\n", |
| call->number, 0); |
| if (err) { |
| LOG_ERR("Fail to send CLIP %d", err); |
| } |
| } |
| } |
| } |
| } |
| |
| static void bt_ag_ringing_work(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct bt_hfp_ag_call *call = CONTAINER_OF(dwork, struct bt_hfp_ag_call, ringing_work); |
| |
| LOG_DBG(""); |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) { |
| return; |
| } |
| |
| (void)hfp_ag_next_step(call->ag, bt_ag_ringing_work_cb, call); |
| } |
| |
| static void bt_ag_send_ok_code(struct bt_hfp_ag *ag) |
| { |
| if (hfp_ag_send_data(ag, NULL, NULL, "\r\nOK\r\n") != 0) { |
| LOG_ERR("Failed to send OK code"); |
| } |
| } |
| |
| static void bt_ag_ongoing_call_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, ongoing_call_work); |
| |
| LOG_DBG(""); |
| |
| if (!atomic_test_and_clear_bit(ag->flags, BT_HGP_AG_ONGOING_CALLS)) { |
| return; |
| } |
| |
| ag->ongoing_call_count = 0; |
| bt_hfp_ag_notify_cind_value(ag); |
| bt_ag_send_ok_code(ag); |
| } |
| |
| static K_KERNEL_STACK_MEMBER(ag_thread_stack, CONFIG_BT_HFP_AG_THREAD_STACK_SIZE); |
| |
| static struct bt_hfp_ag *hfp_ag_create(struct bt_conn *conn) |
| { |
| static struct bt_rfcomm_dlc_ops ops = { |
| .connected = hfp_ag_connected, |
| .disconnected = hfp_ag_disconnected, |
| .recv = hfp_ag_recv, |
| .sent = hfp_ag_sent, |
| }; |
| static k_tid_t ag_thread_id; |
| static struct k_thread ag_thread; |
| size_t index; |
| struct bt_hfp_ag *ag; |
| |
| LOG_DBG("conn %p", conn); |
| |
| if (ag_thread_id == NULL) { |
| |
| k_fifo_init(&ag_tx_free); |
| k_fifo_init(&ag_tx_notify); |
| |
| for (index = 0; index < ARRAY_SIZE(ag_tx); index++) { |
| k_fifo_put(&ag_tx_free, &ag_tx[index]); |
| } |
| |
| ag_thread_id = k_thread_create( |
| &ag_thread, ag_thread_stack, K_KERNEL_STACK_SIZEOF(ag_thread_stack), |
| bt_hfp_ag_thread, NULL, NULL, NULL, |
| K_PRIO_COOP(CONFIG_BT_HFP_AG_THREAD_PRIO), 0, K_NO_WAIT); |
| __ASSERT(ag_thread_id, "Cannot create thread for AG"); |
| k_thread_name_set(ag_thread_id, "HFP AG"); |
| } |
| |
| index = (size_t)bt_conn_index(conn); |
| __ASSERT(index < ARRAY_SIZE(bt_hfp_ag_pool), "Conn index is out of bounds"); |
| |
| ag = &bt_hfp_ag_pool[index]; |
| if (ag->acl_conn) { |
| LOG_ERR("AG connection (%p) is established", conn); |
| return NULL; |
| } |
| |
| (void)memset(ag, 0, sizeof(struct bt_hfp_ag)); |
| |
| sys_slist_init(&ag->tx_pending); |
| |
| k_sem_init(&ag->lock, 1, 1); |
| |
| ag->rfcomm_dlc.ops = &ops; |
| ag->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; |
| |
| /* Set the supported features*/ |
| ag->ag_features = BT_HFP_AG_SUPPORTED_FEATURES; |
| |
| /* Support HF indicators */ |
| if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY)) { |
| ag->hf_indicators_of_ag |= BIT(HFP_HF_ENHANCED_SAFETY_IND); |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY)) { |
| ag->hf_indicators_of_ag |= BIT(HFP_HF_BATTERY_LEVEL_IND); |
| } |
| |
| ag->hf_indicators = ag->hf_indicators_of_ag; |
| |
| /* If supported codec ids cannot be notified, disable codec negotiation. */ |
| if (!(bt_ag && bt_ag->codec)) { |
| ag->ag_features &= ~BT_HFP_AG_FEATURE_CODEC_NEG; |
| } |
| |
| ag->hf_features = 0; |
| ag->hf_codec_ids = 0; |
| |
| ag->acl_conn = conn; |
| |
| /* Set AG indicator value */ |
| ag->indicator_value[BT_HFP_AG_SERVICE_IND] = 0; |
| ag->indicator_value[BT_HFP_AG_CALL_IND] = 0; |
| ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = 0; |
| ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = 0; |
| ag->indicator_value[BT_HFP_AG_SIGNAL_IND] = 0; |
| ag->indicator_value[BT_HFP_AG_ROAM_IND] = 0; |
| ag->indicator_value[BT_HFP_AG_BATTERY_IND] = 0; |
| |
| /* Set AG indicator status */ |
| ag->indicator = BIT(BT_HFP_AG_SERVICE_IND) | BIT(BT_HFP_AG_CALL_IND) | |
| BIT(BT_HFP_AG_CALL_SETUP_IND) | BIT(BT_HFP_AG_CALL_HELD_IND) | |
| BIT(BT_HFP_AG_SIGNAL_IND) | BIT(BT_HFP_AG_ROAM_IND) | |
| BIT(BT_HFP_AG_BATTERY_IND); |
| |
| /* Set AG operator */ |
| memcpy(ag->operator, "UNKNOWN", sizeof("UNKNOWN")); |
| |
| /* Set Codec ID*/ |
| ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; |
| |
| /* Init delay work */ |
| k_work_init_delayable(&ag->tx_work, bt_ag_tx_work); |
| /* Init delay work */ |
| k_work_init_delayable(&ag->ongoing_call_work, bt_ag_ongoing_call_work); |
| |
| return ag; |
| } |
| |
| int bt_hfp_ag_connect(struct bt_conn *conn, struct bt_hfp_ag **ag, uint8_t channel) |
| { |
| struct bt_hfp_ag *new_ag; |
| int err; |
| |
| LOG_DBG(""); |
| |
| if (!conn || !ag || !channel) { |
| return -EINVAL; |
| } |
| |
| if (!bt_ag) { |
| return -EFAULT; |
| } |
| |
| new_ag = hfp_ag_create(conn); |
| if (!new_ag) { |
| return -ECONNREFUSED; |
| } |
| |
| err = bt_rfcomm_dlc_connect(conn, &new_ag->rfcomm_dlc, channel); |
| if (err != 0) { |
| (void)memset(new_ag, 0, sizeof(*new_ag)); |
| *ag = NULL; |
| } else { |
| *ag = new_ag; |
| bt_hfp_ag_set_state(*ag, BT_HFP_CONNECTING); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag) |
| { |
| LOG_DBG(""); |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTING); |
| |
| return bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); |
| } |
| |
| static int hfp_ag_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, |
| struct bt_rfcomm_dlc **dlc) |
| { |
| struct bt_hfp_ag *ag; |
| |
| ag = hfp_ag_create(conn); |
| |
| if (!ag) { |
| return -ECONNREFUSED; |
| } |
| |
| *dlc = &ag->rfcomm_dlc; |
| |
| return 0; |
| } |
| |
| static int bt_hfp_ag_sco_accept(const struct bt_sco_accept_info *info, |
| struct bt_sco_chan **chan) |
| { |
| static const struct bt_sco_chan_ops ops = { |
| .connected = hfp_ag_sco_connected, |
| .disconnected = hfp_ag_sco_disconnected, |
| }; |
| size_t index; |
| struct bt_hfp_ag *ag; |
| |
| LOG_DBG("conn %p", info->acl); |
| |
| index = (size_t)bt_conn_index(info->acl); |
| __ASSERT(index < ARRAY_SIZE(bt_hfp_ag_pool), "Conn index is out of bounds"); |
| |
| ag = &bt_hfp_ag_pool[index]; |
| if (ag->acl_conn != info->acl) { |
| LOG_ERR("ACL %p of AG is unaligned with SCO's %p", ag->acl_conn, info->acl); |
| return -EINVAL; |
| } |
| |
| if (ag->sco_chan.sco) { |
| return -ECONNREFUSED; |
| } |
| |
| ag->sco_chan.ops = &ops; |
| |
| *chan = &ag->sco_chan; |
| |
| return 0; |
| } |
| |
| static void ag_sco_connected(struct bt_conn *conn, uint8_t err) |
| { |
| ARG_UNUSED(conn); |
| ARG_UNUSED(err); |
| } |
| |
| static void ag_sco_disconnected(struct bt_conn *conn, uint8_t reason) |
| { |
| ARG_UNUSED(reason); |
| |
| __ASSERT(conn != NULL, "Invalid SCO conn"); |
| |
| ARRAY_FOR_EACH(bt_hfp_ag_pool, i) { |
| if (bt_hfp_ag_pool[i].sco_conn == conn) { |
| bt_conn_unref(bt_hfp_ag_pool[i].sco_conn); |
| bt_hfp_ag_pool[i].sco_conn = NULL; |
| } |
| } |
| } |
| |
| static struct bt_sco_conn_cb ag_sco_conn_cb = { |
| .connected = ag_sco_connected, |
| .disconnected = ag_sco_disconnected |
| }; |
| |
| static void hfp_ag_init(void) |
| { |
| static struct bt_rfcomm_server chan = { |
| .channel = BT_RFCOMM_CHAN_HFP_AG, |
| .accept = hfp_ag_accept, |
| }; |
| |
| bt_rfcomm_server_register(&chan); |
| |
| static struct bt_sco_server sco_server = { |
| .sec_level = BT_SECURITY_L0, |
| .accept = bt_hfp_ag_sco_accept, |
| }; |
| |
| bt_sco_server_register(&sco_server); |
| |
| bt_sdp_register_service(&hfp_ag_rec); |
| |
| bt_sco_conn_cb_register(&ag_sco_conn_cb); |
| } |
| |
| int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb) |
| { |
| if (!cb) { |
| return -EINVAL; |
| } |
| |
| if (bt_ag) { |
| return -EALREADY; |
| } |
| |
| bt_ag = cb; |
| |
| hfp_ag_init(); |
| |
| return 0; |
| } |
| |
| static void bt_hfp_ag_incoming_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| __ASSERT(call, "Invalid call object"); |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_INCOMING); |
| |
| if (bt_ag && bt_ag->incoming) { |
| bt_ag->incoming(ag, call, call->number); |
| } |
| |
| if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { |
| int err; |
| |
| err = bt_hfp_ag_create_audio_connection(ag, call); |
| if (err) { |
| bt_hfp_ag_call_reject(ag, user_data); |
| } |
| } else { |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); |
| bt_hfp_ag_call_ringing_cb(call, false); |
| } |
| } |
| |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| static void bt_hfp_ag_2nd_incoming_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call; |
| |
| call = (struct bt_hfp_ag_call *)user_data; |
| |
| __ASSERT(call, "Invalid call object"); |
| |
| if (bt_ag && bt_ag->incoming) { |
| bt_ag->incoming(ag, call, call->number); |
| } |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); |
| } |
| |
| static void bt_hfp_ag_ccwa_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| int err; |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_INCOMING, |
| bt_hfp_ag_2nd_incoming_cb, user_data); |
| if (err) { |
| LOG_ERR("Fail to send call setup"); |
| } |
| } |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| |
| int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) |
| { |
| int err = 0; |
| size_t len; |
| struct bt_hfp_ag_call *call; |
| int call_count; |
| |
| 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); |
| |
| len = strlen(number); |
| if ((len == 0) || (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN)) { |
| return -EINVAL; |
| } |
| |
| call = get_call_from_number(ag, number, 0); |
| if (call) { |
| return -EBUSY; |
| } |
| |
| call_count = get_none_released_calls(ag); |
| if (call_count) { |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, |
| BT_HFP_AG_FEATURE_3WAY_CALL)) { |
| LOG_WRN("3 Way call feature is not supported on both sides"); |
| return -ENOEXEC; |
| } |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_CCWA_ENABLE)) { |
| LOG_WRN("Call waiting notification is not enabled"); |
| return -ENOTSUP; |
| } |
| |
| if (!get_active_held_calls(ag)) { |
| LOG_WRN("The first call is not accepted"); |
| return -EBUSY; |
| } |
| #else |
| return -EBUSY; |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| } |
| |
| call = get_new_call(ag, number, 0); |
| if (!call) { |
| LOG_WRN("Cannot allocate a new call object"); |
| return -ENOMEM; |
| } |
| |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING); |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_INCOMING); |
| |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| if (call_count) { |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY); |
| err = hfp_ag_send_data(ag, bt_hfp_ag_ccwa_cb, call, "\r\n+CCWA:\"%s\",%d\r\n", |
| call->number, call->type); |
| if (err) { |
| LOG_WRN("Fail to send call waiting notification"); |
| free_call(call); |
| } |
| |
| return err; |
| } |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_INCOMING, |
| bt_hfp_ag_incoming_cb, call); |
| if (err) { |
| free_call(call); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_hold_incoming(struct bt_hfp_ag_call *call) |
| { |
| int err = 0; |
| struct bt_hfp_ag *ag; |
| bt_hfp_call_state_t call_state; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| call_state = call->call_state; |
| hfp_ag_unlock(ag); |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| return -EINVAL; |
| } |
| |
| if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { |
| atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); |
| err = hfp_ag_send_data(ag, btrh_held_cb, call, "\r\n+BTRH:%d\r\n", |
| BT_HFP_BTRH_ON_HOLD); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| |
| return -EINVAL; |
| } |
| |
| int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) |
| { |
| int err = 0; |
| struct bt_hfp_ag *ag; |
| bt_hfp_call_state_t call_state; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| call_state = call->call_state; |
| hfp_ag_unlock(ag); |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| return -EINVAL; |
| } |
| |
| if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_REJECT_CALL)) { |
| LOG_ERR("AG has not ability to reject call"); |
| return -ENOTSUP; |
| } |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { |
| if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { |
| uint8_t call_setup; |
| |
| hfp_ag_lock(ag); |
| call_setup = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; |
| hfp_ag_unlock(ag); |
| |
| if (call_setup) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, |
| BT_HFP_CALL_SETUP_NONE, NULL, NULL); |
| if (err) { |
| return err; |
| } |
| } |
| |
| ag_reject_call(call); |
| |
| return 0; |
| } |
| } |
| |
| if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_reject_cb, call); |
| |
| return err; |
| } |
| |
| if ((call_state == BT_HFP_CALL_ACTIVE) && |
| atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) { |
| err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", |
| BT_HFP_BTRH_REJECTED); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| |
| return -EINVAL; |
| } |
| |
| int bt_hfp_ag_accept(struct bt_hfp_ag_call *call) |
| { |
| int err = 0; |
| struct bt_hfp_ag *ag; |
| bt_hfp_call_state_t call_state; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| call_state = call->call_state; |
| hfp_ag_unlock(ag); |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| return -EINVAL; |
| } |
| |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) { |
| if ((call_state == BT_HFP_CALL_ALERTING) || (call_state == BT_HFP_CALL_INCOMING)) { |
| return chld_deactivate_calls_and_accept_other_call(ag, false); |
| } |
| } |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| |
| if (call_state == BT_HFP_CALL_ALERTING) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, call); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_call_setup_none_cb, call); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD) && |
| (call_state == BT_HFP_CALL_ACTIVE)) { |
| err = hfp_ag_send_data(ag, btrh_accept_cb, call, "\r\n+BTRH:%d\r\n", |
| BT_HFP_BTRH_ACCEPTED); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| |
| return -EINVAL; |
| } |
| |
| int bt_hfp_ag_terminate(struct bt_hfp_ag_call *call) |
| { |
| int err = 0; |
| struct bt_hfp_ag *ag; |
| bt_hfp_call_state_t call_state; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| call_state = call->call_state; |
| hfp_ag_unlock(ag); |
| |
| if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD) && |
| atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) && |
| (call_state == BT_HFP_CALL_ACTIVE)) { |
| err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", |
| BT_HFP_BTRH_REJECTED); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| return err; |
| } |
| |
| if ((call_state != BT_HFP_CALL_ACTIVE) && (call_state != BT_HFP_CALL_HOLD)) { |
| return -EINVAL; |
| } |
| |
| ag_terminate_call(call); |
| |
| return 0; |
| } |
| |
| int bt_hfp_ag_retrieve(struct bt_hfp_ag_call *call) |
| { |
| struct bt_hfp_ag *ag; |
| bt_hfp_call_state_t call_state; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| call_state = call->call_state; |
| hfp_ag_unlock(ag); |
| |
| if (call_state != BT_HFP_CALL_HOLD) { |
| return -EINVAL; |
| } |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); |
| |
| if (bt_ag && bt_ag->retrieve) { |
| bt_ag->retrieve(call); |
| } |
| |
| ag_notify_call_held_ind(ag, NULL); |
| |
| return 0; |
| } |
| |
| int bt_hfp_ag_hold(struct bt_hfp_ag_call *call) |
| { |
| struct bt_hfp_ag *ag; |
| bt_hfp_call_state_t call_state; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| call_state = call->call_state; |
| hfp_ag_unlock(ag); |
| |
| if (call_state != BT_HFP_CALL_ACTIVE) { |
| return -EINVAL; |
| } |
| |
| if ((call_state == BT_HFP_CALL_ACTIVE) && |
| (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) { |
| return -EINVAL; |
| } |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); |
| |
| if (bt_ag && bt_ag->held) { |
| bt_ag->held(call); |
| } |
| |
| ag_notify_call_held_ind(ag, NULL); |
| |
| return 0; |
| } |
| |
| int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number) |
| { |
| 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); |
| |
| return bt_hfp_ag_outgoing_call(ag, number, 0); |
| } |
| |
| static void bt_hfp_ag_ringing_cb(struct bt_hfp_ag *ag, void *user_data) |
| { |
| struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; |
| |
| bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); |
| |
| if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) { |
| bt_hfp_ag_call_ringing_cb(call, true); |
| } else { |
| bt_hfp_ag_call_ringing_cb(call, false); |
| } |
| } |
| |
| int bt_hfp_ag_remote_ringing(struct bt_hfp_ag_call *call) |
| { |
| int err = 0; |
| struct bt_hfp_ag *ag; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| if (call->call_state != BT_HFP_CALL_OUTGOING) { |
| hfp_ag_unlock(ag); |
| return -EBUSY; |
| } |
| |
| if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING) && (ag->sco_conn == 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, |
| call); |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_remote_reject(struct bt_hfp_ag_call *call) |
| { |
| int err; |
| struct bt_hfp_ag *ag; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| if ((call->call_state != BT_HFP_CALL_ALERTING) && |
| (call->call_state != BT_HFP_CALL_OUTGOING)) { |
| hfp_ag_unlock(ag); |
| return -EINVAL; |
| } |
| hfp_ag_unlock(ag); |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| return -EINVAL; |
| } |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_reject_cb, call); |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_remote_accept(struct bt_hfp_ag_call *call) |
| { |
| int err; |
| struct bt_hfp_ag *ag; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| if (call->call_state != BT_HFP_CALL_ALERTING) { |
| hfp_ag_unlock(ag); |
| return -EINVAL; |
| } |
| hfp_ag_unlock(ag); |
| |
| if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) { |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) { |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, bt_hfp_ag_accept_cb, call); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| } |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, BT_HFP_CALL_SETUP_NONE, |
| bt_hfp_ag_call_setup_none_cb, call); |
| if (err) { |
| LOG_ERR("Fail to send err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_remote_terminate(struct bt_hfp_ag_call *call) |
| { |
| struct bt_hfp_ag *ag; |
| |
| LOG_DBG(""); |
| |
| if (call == NULL) { |
| return -EINVAL; |
| } |
| |
| ag = call->ag; |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| if ((call->call_state != BT_HFP_CALL_ACTIVE) && (call->call_state != BT_HFP_CALL_HOLD)) { |
| hfp_ag_unlock(ag); |
| return -EINVAL; |
| } |
| hfp_ag_unlock(ag); |
| |
| ag_terminate_call(call); |
| |
| return 0; |
| } |
| |
| int bt_hfp_ag_explicit_call_transfer(struct bt_hfp_ag *ag) |
| { |
| #if defined(CONFIG_BT_HFP_AG_3WAY_CALL) |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| hfp_ag_unlock(ag); |
| |
| return chld_drop_conversation(ag); |
| #else |
| return -ENOTSUP; |
| #endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ |
| } |
| |
| 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: |
| case BT_HFP_AG_SIGNAL_IND: |
| case BT_HFP_AG_ROAM_IND: |
| case BT_HFP_AG_BATTERY_IND: |
| if ((ag_ind[(uint8_t)index].min > value) || (ag_ind[(uint8_t)index].max < value)) { |
| return -EINVAL; |
| } |
| break; |
| case BT_HFP_AG_CALL_IND: |
| case BT_HFP_AG_CALL_SETUP_IND: |
| case BT_HFP_AG_CALL_HELD_IND: |
| 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, uint8_t mode, 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'; |
| ag->mode = mode; |
| hfp_ag_unlock(ag); |
| |
| return 0; |
| } |
| |
| int bt_hfp_ag_audio_connect(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) { |
| if (!(ag->hf_codec_ids & BIT(id))) { |
| hfp_ag_unlock(ag); |
| return -EINVAL; |
| } |
| } else { |
| if (id != BT_HFP_AG_CODEC_CVSD) { |
| hfp_ag_unlock(ag); |
| return -ENOTSUP; |
| } |
| } |
| |
| if (ag->sco_conn != NULL) { |
| LOG_ERR("Audio conenction has been connected"); |
| hfp_ag_unlock(ag); |
| return -ECONNREFUSED; |
| } |
| hfp_ag_unlock(ag); |
| |
| if (atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) { |
| return -EBUSY; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->selected_codec_id != id) { |
| atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); |
| } |
| ag->selected_codec_id = id; |
| hfp_ag_unlock(ag); |
| |
| err = bt_hfp_ag_create_audio_connection(ag, NULL); |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm) |
| { |
| int err; |
| |
| LOG_DBG(""); |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| if (vgm > BT_HFP_HF_VGM_GAIN_MAX) { |
| LOG_ERR("Invalid VGM (%d>%d)", vgm, BT_HFP_HF_VGM_GAIN_MAX); |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| hfp_ag_unlock(ag); |
| |
| if (!HF_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOLUME)) { |
| LOG_ERR("Remote Audio Volume Control is unsupported"); |
| return -ENOTSUP; |
| } |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGM=%d\r\n", vgm); |
| if (err) { |
| LOG_ERR("Fail to notify vgm err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs) |
| { |
| int err; |
| |
| LOG_DBG(""); |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| if (vgs > BT_HFP_HF_VGS_GAIN_MAX) { |
| LOG_ERR("Invalid VGM (%d>%d)", vgs, BT_HFP_HF_VGS_GAIN_MAX); |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| hfp_ag_unlock(ag); |
| |
| if (!HF_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOLUME)) { |
| LOG_ERR("Remote Audio Volume Control is unsupported"); |
| return -ENOTSUP; |
| } |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGS=%d\r\n", vgs); |
| if (err) { |
| LOG_ERR("Fail to notify vgs err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband) |
| { |
| 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); |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR=%d\r\n", inband ? 1 : 0); |
| if (err) { |
| LOG_ERR("Fail to set inband ringtone err :(%d)", err); |
| return err; |
| } |
| |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, inband); |
| return 0; |
| } |
| |
| int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate) |
| { |
| #if defined(CONFIG_BT_HFP_AG_VOICE_RECG) |
| int err; |
| bool feature; |
| char *bvra; |
| |
| 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); |
| |
| if (activate && atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { |
| LOG_WRN("VR has been activated"); |
| return -ENOTSUP; |
| } else if (!activate && !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { |
| LOG_WRN("VR is not activated"); |
| return -ENOTSUP; |
| } |
| |
| feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, |
| BT_HFP_AG_FEATURE_ENH_VOICE_RECG); |
| if (!feature) { |
| bvra = ""; |
| } else { |
| bvra = ",0"; |
| } |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:%d%s\r\n", activate, bvra); |
| if (err) { |
| LOG_ERR("Fail to notify VR activation :(%d)", err); |
| return err; |
| } |
| |
| atomic_set_bit_to(ag->flags, BT_HFP_AG_VRE_ACTIVATE, activate); |
| atomic_clear_bit(ag->flags, BT_HFP_AG_VRE_R2A); |
| return 0; |
| #else |
| return -ENOTSUP; |
| #endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ |
| } |
| |
| int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state) |
| { |
| #if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) |
| int err; |
| bool feature; |
| |
| 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); |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { |
| LOG_WRN("Voice Recognition is not activated"); |
| return -EINVAL; |
| } |
| |
| feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, |
| BT_HFP_AG_FEATURE_ENH_VOICE_RECG); |
| if (!feature) { |
| return -ENOTSUP; |
| } |
| |
| if ((state & BT_HFP_BVRA_STATE_SEND_AUDIO) && |
| !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { |
| LOG_ERR("HFP HF is not ready to accept audio input"); |
| return -EINVAL; |
| } |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:1,%d\r\n", state); |
| if (err) { |
| LOG_ERR("Fail to send state of VRE :(%d)", err); |
| return err; |
| } |
| |
| return 0; |
| #else |
| return -ENOTSUP; |
| #endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ |
| } |
| |
| int bt_hfp_ag_vre_textual_representation(struct bt_hfp_ag *ag, uint8_t state, const char *id, |
| uint8_t type, uint8_t operation, const char *text) |
| { |
| #if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) |
| int err; |
| bool feature; |
| |
| LOG_DBG(""); |
| |
| 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); |
| |
| if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) { |
| LOG_WRN("Voice Recognition is not activated"); |
| return -EINVAL; |
| } |
| |
| feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, |
| BT_HFP_AG_FEATURE_ENH_VOICE_RECG); |
| if (!feature) { |
| return -ENOTSUP; |
| } |
| |
| feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOICE_RECG_TEXT, |
| BT_HFP_AG_FEATURE_VOICE_RECG_TEXT); |
| if (!feature) { |
| return -ENOTSUP; |
| } |
| |
| if ((state & BT_HFP_BVRA_STATE_SEND_AUDIO) && |
| !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) { |
| LOG_ERR("HFP HF is not ready to accept audio input"); |
| return -EINVAL; |
| } |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:1,%d,%s,%d,%d,\"%s\"\r\n", |
| state, id, type, operation, text); |
| if (err) { |
| LOG_ERR("Fail to send state of VRE :(%d)", err); |
| return err; |
| } |
| |
| return 0; |
| #else |
| return -ENOTSUP; |
| #endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ |
| } |
| |
| int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength) |
| { |
| 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); |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_SIGNAL_IND, strength, |
| NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to set signal strength err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status) |
| { |
| 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); |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_ROAM_IND, status, |
| NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to set roaming status err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level) |
| { |
| 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); |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_BATTERY_IND, level, |
| NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to set battery level err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available) |
| { |
| 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); |
| |
| err = hfp_ag_update_indicator(ag, BT_HFP_AG_SERVICE_IND, |
| available ? 1 : 0, NULL, NULL); |
| if (err) { |
| LOG_ERR("Fail to set service availability err :(%d)", err); |
| } |
| |
| return err; |
| } |
| |
| int bt_hfp_ag_hf_indicator(struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, bool enable) |
| { |
| #if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) |
| int err; |
| uint32_t supported_indicators; |
| |
| LOG_DBG(""); |
| |
| if (ag == NULL) { |
| return -EINVAL; |
| } |
| |
| hfp_ag_lock(ag); |
| if (ag->state != BT_HFP_CONNECTED) { |
| hfp_ag_unlock(ag); |
| return -ENOTCONN; |
| } |
| |
| supported_indicators = ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; |
| hfp_ag_unlock(ag); |
| |
| if (!(supported_indicators & BIT(indicator))) { |
| LOG_ERR("Unsupported indicator %d", indicator); |
| return -ENOTSUP; |
| } |
| |
| hfp_ag_lock(ag); |
| if (enable) { |
| ag->hf_indicators |= BIT(indicator); |
| } else { |
| ag->hf_indicators &= ~BIT(indicator); |
| } |
| hfp_ag_unlock(ag); |
| |
| err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", |
| indicator, enable ? 1 : 0); |
| if (err) { |
| LOG_ERR("Fail to update registration status of indicator:(%d)", err); |
| } |
| |
| return err; |
| #else |
| return -ENOTSUP; |
| #endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ |
| } |
| |
| int bt_hfp_ag_ongoing_calls(struct bt_hfp_ag *ag, struct bt_hfp_ag_ongoing_call *calls, |
| size_t count) |
| { |
| struct bt_hfp_ag_ongoing_call *call; |
| bool valid; |
| size_t len; |
| int err = -EINVAL; |
| |
| LOG_DBG(""); |
| |
| if (ag == NULL) { |
| LOG_ERR("Invalid AG object"); |
| return -EINVAL; |
| } |
| |
| if (count > ARRAY_SIZE(ag->ongoing_calls)) { |
| LOG_ERR("Out of buffer (%u > %u)", count, ARRAY_SIZE(ag->ongoing_calls)); |
| return -ENOMEM; |
| } |
| |
| if (!atomic_test_and_clear_bit(ag->flags, BT_HGP_AG_ONGOING_CALLS)) { |
| LOG_ERR("Cannot set ongoing calls"); |
| return -ESRCH; |
| } |
| |
| for (ag->ongoing_call_count = 0; ag->ongoing_call_count < count; ag->ongoing_call_count++) { |
| call = &calls[ag->ongoing_call_count]; |
| valid = true; |
| |
| len = strlen(call->number); |
| if ((len == 0) || (len >= ARRAY_SIZE(call->number))) { |
| LOG_WRN("Invalid call number"); |
| /* Clear all calls */ |
| ag->ongoing_call_count = 0; |
| goto failed; |
| } |
| |
| switch (call->status) { |
| case BT_HFP_AG_CALL_STATUS_DIALING: |
| case BT_HFP_AG_CALL_STATUS_ALERTING: |
| if (call->dir == BT_HFP_AG_CALL_DIR_INCOMING) { |
| LOG_ERR("Dialing call cannot be incoming"); |
| valid = false; |
| } |
| break; |
| case BT_HFP_AG_CALL_STATUS_INCOMING: |
| case BT_HFP_AG_CALL_STATUS_WAITING: |
| case BT_HFP_AG_CALL_STATUS_INCOMING_HELD: |
| if (call->dir == BT_HFP_AG_CALL_DIR_OUTGOING) { |
| LOG_ERR("Incoming call cannot be outgoing"); |
| valid = false; |
| } |
| break; |
| case BT_HFP_AG_CALL_STATUS_ACTIVE: |
| case BT_HFP_AG_CALL_STATUS_HELD: |
| break; |
| default: |
| LOG_ERR("Invalid status"); |
| valid = false; |
| break; |
| } |
| |
| if (!valid) { |
| /* Clear all calls */ |
| ag->ongoing_call_count = 0; |
| goto failed; |
| } |
| |
| memcpy(&ag->ongoing_calls[ag->ongoing_call_count], call, |
| sizeof(ag->ongoing_calls[ag->ongoing_call_count])); |
| } |
| |
| if (ag->ongoing_call_count > 1) { |
| switch (ag->ongoing_calls[0].status) { |
| case BT_HFP_AG_CALL_STATUS_ACTIVE: |
| case BT_HFP_AG_CALL_STATUS_HELD: |
| case BT_HFP_AG_CALL_STATUS_INCOMING_HELD: |
| break; |
| default: |
| LOG_ERR("Unexpected call status for multiple calls"); |
| /* Clear all calls */ |
| ag->ongoing_call_count = 0; |
| goto failed; |
| } |
| } |
| |
| err = 0; |
| |
| failed: |
| k_work_cancel_delayable(&ag->ongoing_call_work); |
| bt_hfp_ag_notify_cind_value(ag); |
| bt_ag_send_ok_code(ag); |
| return err; |
| } |