To make it easier to understand the code, the following was done: - Use INITIATING/ADV for state names that are exclusive to central or peripheral. Previously it was not necessarily clear that the state BT_CONN_CONNECTING was for central only by just looking at where it was used. The terms INITIATING/ADV were used in favor of central and peripheral as these terms also work for SCO connection establishment. - BT_CONN_CONNECTING_SCAN -> BT_CONN_SCAN_BEFORE_INITIATING to make it more clear that we are not scanning and connecting at the same time. The new name should make it more clear why we are scanning - only with the intention to start the initiator later. - BT_CONN_CONNECTING_AUTO -> BT_CONN_INITIATING_FILTER_LIST. This makes it clear that this state is something different than BT_CONN_AUTO_CONNECT. Signed-off-by: Rubin Gerritsen <rubin.gerritsen@nordicsemi.no>
406 lines
8.7 KiB
C
406 lines
8.7 KiB
C
/* sco.c - Bluetooth sco handling */
|
|
|
|
/*
|
|
* Copyright (c) 2015-2016 Intel Corporation
|
|
* Copyright 2024 NXP
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <errno.h>
|
|
|
|
#include <zephyr/sys/atomic.h>
|
|
#include <zephyr/sys/check.h>
|
|
|
|
#include <zephyr/bluetooth/hci.h>
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
|
|
#include "common/bt_str.h"
|
|
|
|
#include "host/addr_internal.h"
|
|
#include "host/hci_core.h"
|
|
#include "br.h"
|
|
#include "host/conn_internal.h"
|
|
#include "sco_internal.h"
|
|
|
|
#define LOG_LEVEL CONFIG_BT_CONN_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_sco);
|
|
|
|
struct bt_sco_server *sco_server;
|
|
|
|
#define SCO_CHAN(_sco) ((_sco)->sco.chan);
|
|
|
|
int bt_sco_server_register(struct bt_sco_server *server)
|
|
{
|
|
CHECKIF(!server) {
|
|
LOG_DBG("Invalid parameter: server %p", server);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sco_server) {
|
|
return -EADDRINUSE;
|
|
}
|
|
|
|
if (!server->accept) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (server->sec_level > BT_SECURITY_L3) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("%p", server);
|
|
|
|
sco_server = server;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_sco_server_unregister(struct bt_sco_server *server)
|
|
{
|
|
CHECKIF(!server) {
|
|
LOG_DBG("Invalid parameter: server %p", server);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (sco_server != server) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
sco_server = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bt_sco_connected(struct bt_conn *sco)
|
|
{
|
|
struct bt_sco_chan *chan;
|
|
|
|
if (sco == NULL || sco->type != BT_CONN_TYPE_SCO) {
|
|
LOG_ERR("Invalid parameters: sco %p sco->type %u", sco, sco ? sco->type : 0);
|
|
return;
|
|
}
|
|
|
|
LOG_DBG("%p", sco);
|
|
|
|
chan = SCO_CHAN(sco);
|
|
|
|
if (chan == NULL) {
|
|
LOG_ERR("Could not lookup chan from connected SCO");
|
|
return;
|
|
}
|
|
|
|
bt_sco_chan_set_state(chan, BT_SCO_STATE_CONNECTED);
|
|
|
|
if (chan->ops && chan->ops->connected) {
|
|
chan->ops->connected(chan);
|
|
}
|
|
}
|
|
|
|
void bt_sco_disconnected(struct bt_conn *sco)
|
|
{
|
|
struct bt_sco_chan *chan;
|
|
|
|
if (sco == NULL || sco->type != BT_CONN_TYPE_SCO) {
|
|
LOG_ERR("Invalid parameters: sco %p sco->type %u", sco, sco ? sco->type : 0);
|
|
return;
|
|
}
|
|
LOG_DBG("%p", sco);
|
|
|
|
chan = SCO_CHAN(sco);
|
|
if (chan == NULL) {
|
|
LOG_ERR("Could not lookup chan from connected SCO");
|
|
return;
|
|
}
|
|
|
|
bt_sco_chan_set_state(chan, BT_SCO_STATE_DISCONNECTED);
|
|
|
|
bt_sco_cleanup_acl(sco);
|
|
|
|
chan->sco = NULL;
|
|
|
|
if (chan->ops && chan->ops->disconnected) {
|
|
chan->ops->disconnected(chan, sco->err);
|
|
}
|
|
}
|
|
|
|
static uint8_t sco_server_check_security(struct bt_conn *conn)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_CONN_DISABLE_SECURITY)) {
|
|
return BT_HCI_ERR_SUCCESS;
|
|
}
|
|
|
|
if (conn->sec_level >= sco_server->sec_level) {
|
|
return BT_HCI_ERR_SUCCESS;
|
|
}
|
|
|
|
return BT_HCI_ERR_INSUFFICIENT_SECURITY;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_CONN_LOG_LEVEL_DBG)
|
|
const char *bt_sco_chan_state_str(uint8_t state)
|
|
{
|
|
switch (state) {
|
|
case BT_SCO_STATE_DISCONNECTED:
|
|
return "disconnected";
|
|
case BT_SCO_STATE_CONNECTING:
|
|
return "connecting";
|
|
case BT_SCO_STATE_ENCRYPT_PENDING:
|
|
return "encryption pending";
|
|
case BT_SCO_STATE_CONNECTED:
|
|
return "connected";
|
|
case BT_SCO_STATE_DISCONNECTING:
|
|
return "disconnecting";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
void bt_sco_chan_set_state_debug(struct bt_sco_chan *chan,
|
|
enum bt_sco_state state,
|
|
const char *func, int line)
|
|
{
|
|
LOG_DBG("chan %p sco %p %s -> %s", chan, chan->sco, bt_sco_chan_state_str(chan->state),
|
|
bt_sco_chan_state_str(state));
|
|
|
|
/* check transitions validness */
|
|
switch (state) {
|
|
case BT_SCO_STATE_DISCONNECTED:
|
|
/* regardless of old state always allows this states */
|
|
break;
|
|
case BT_SCO_STATE_ENCRYPT_PENDING:
|
|
__fallthrough;
|
|
case BT_SCO_STATE_CONNECTING:
|
|
if (chan->state != BT_SCO_STATE_DISCONNECTED) {
|
|
LOG_WRN("%s()%d: invalid transition", func, line);
|
|
}
|
|
break;
|
|
case BT_SCO_STATE_CONNECTED:
|
|
if (chan->state != BT_SCO_STATE_CONNECTING) {
|
|
LOG_WRN("%s()%d: invalid transition", func, line);
|
|
}
|
|
break;
|
|
case BT_SCO_STATE_DISCONNECTING:
|
|
if (chan->state != BT_SCO_STATE_CONNECTING &&
|
|
chan->state != BT_SCO_STATE_CONNECTED) {
|
|
LOG_WRN("%s()%d: invalid transition", func, line);
|
|
}
|
|
break;
|
|
default:
|
|
LOG_ERR("%s()%d: unknown (%u) state was set", func, line, state);
|
|
return;
|
|
}
|
|
|
|
chan->state = state;
|
|
}
|
|
#else
|
|
void bt_sco_chan_set_state(struct bt_sco_chan *chan, enum bt_sco_state state)
|
|
{
|
|
chan->state = state;
|
|
}
|
|
#endif /* CONFIG_BT_CONN_LOG_LEVEL_DBG */
|
|
|
|
|
|
static void bt_sco_chan_add(struct bt_conn *sco, struct bt_sco_chan *chan)
|
|
{
|
|
/* Attach SCO channel to the connection */
|
|
chan->sco = sco;
|
|
sco->sco.chan = chan;
|
|
|
|
LOG_DBG("sco %p chan %p", sco, chan);
|
|
}
|
|
|
|
static int sco_accept(struct bt_conn *acl, struct bt_conn *sco)
|
|
{
|
|
struct bt_sco_accept_info accept_info;
|
|
struct bt_sco_chan *chan;
|
|
int err;
|
|
|
|
CHECKIF(!sco || sco->type != BT_CONN_TYPE_SCO) {
|
|
LOG_ERR("Invalid parameters: sco %p sco->type %u", sco, sco ? sco->type : 0);
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("%p", sco);
|
|
|
|
accept_info.acl = acl;
|
|
memcpy(accept_info.dev_class, sco->sco.dev_class, sizeof(accept_info.dev_class));
|
|
accept_info.link_type = sco->sco.link_type;
|
|
|
|
err = sco_server->accept(&accept_info, &chan);
|
|
if (err < 0) {
|
|
LOG_ERR("Server failed to accept: %d", err);
|
|
return err;
|
|
}
|
|
|
|
if (chan->ops == NULL) {
|
|
LOG_ERR("invalid parameter: chan %p chan->ops %p", chan, chan->ops);
|
|
return -EINVAL;
|
|
}
|
|
|
|
bt_sco_chan_add(sco, chan);
|
|
bt_sco_chan_set_state(chan, BT_SCO_STATE_CONNECTING);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int accept_sco_conn(const bt_addr_t *bdaddr, struct bt_conn *sco_conn)
|
|
{
|
|
struct bt_hci_cp_accept_sync_conn_req *cp;
|
|
struct net_buf *buf;
|
|
int err;
|
|
|
|
err = sco_accept(sco_conn->sco.acl, sco_conn);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_ACCEPT_SYNC_CONN_REQ, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
bt_addr_copy(&cp->bdaddr, bdaddr);
|
|
cp->pkt_type = sco_conn->sco.pkt_type;
|
|
cp->tx_bandwidth = 0x00001f40;
|
|
cp->rx_bandwidth = 0x00001f40;
|
|
cp->max_latency = 0x0007;
|
|
cp->retrans_effort = 0x01;
|
|
cp->content_format = BT_VOICE_CVSD_16BIT;
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_ACCEPT_SYNC_CONN_REQ, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8_t bt_esco_conn_req(struct bt_hci_evt_conn_request *evt)
|
|
{
|
|
struct bt_conn *sco_conn;
|
|
uint8_t sec_err;
|
|
|
|
if (sco_server == NULL) {
|
|
LOG_ERR("No SCO server registered");
|
|
return BT_HCI_ERR_UNSPECIFIED;
|
|
}
|
|
|
|
sco_conn = bt_conn_add_sco(&evt->bdaddr, evt->link_type);
|
|
if (!sco_conn) {
|
|
return BT_HCI_ERR_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
sec_err = sco_server_check_security(sco_conn->sco.acl);
|
|
if (BT_HCI_ERR_SUCCESS != sec_err) {
|
|
LOG_DBG("Insufficient security %u", sec_err);
|
|
bt_sco_cleanup(sco_conn);
|
|
return sec_err;
|
|
}
|
|
|
|
memcpy(sco_conn->sco.dev_class, evt->dev_class, sizeof(sco_conn->sco.dev_class));
|
|
sco_conn->sco.link_type = evt->link_type;
|
|
|
|
if (accept_sco_conn(&evt->bdaddr, sco_conn)) {
|
|
LOG_ERR("Error accepting connection from %s", bt_addr_str(&evt->bdaddr));
|
|
bt_sco_cleanup(sco_conn);
|
|
return BT_HCI_ERR_UNSPECIFIED;
|
|
}
|
|
|
|
sco_conn->role = BT_HCI_ROLE_PERIPHERAL;
|
|
bt_conn_set_state(sco_conn, BT_CONN_INITIATING);
|
|
bt_conn_unref(sco_conn);
|
|
|
|
return BT_HCI_ERR_SUCCESS;
|
|
}
|
|
|
|
void bt_sco_cleanup_acl(struct bt_conn *sco)
|
|
{
|
|
LOG_DBG("%p", sco);
|
|
|
|
if (sco->sco.acl) {
|
|
bt_conn_unref(sco->sco.acl);
|
|
sco->sco.acl = NULL;
|
|
}
|
|
}
|
|
|
|
static int sco_setup_sync_conn(struct bt_conn *sco_conn)
|
|
{
|
|
struct net_buf *buf;
|
|
struct bt_hci_cp_setup_sync_conn *cp;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_SETUP_SYNC_CONN, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
|
|
(void)memset(cp, 0, sizeof(*cp));
|
|
|
|
LOG_DBG("handle : %x", sco_conn->sco.acl->handle);
|
|
|
|
cp->handle = sco_conn->sco.acl->handle;
|
|
cp->pkt_type = sco_conn->sco.pkt_type;
|
|
cp->tx_bandwidth = 0x00001f40;
|
|
cp->rx_bandwidth = 0x00001f40;
|
|
cp->max_latency = 0x0007;
|
|
cp->retrans_effort = 0x01;
|
|
cp->content_format = BT_VOICE_CVSD_16BIT;
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_SETUP_SYNC_CONN, buf, NULL);
|
|
if (err < 0) {
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct bt_conn *bt_conn_create_sco(const bt_addr_t *peer, struct bt_sco_chan *chan)
|
|
{
|
|
struct bt_conn *sco_conn;
|
|
int link_type;
|
|
|
|
sco_conn = bt_conn_lookup_addr_sco(peer);
|
|
if (sco_conn) {
|
|
switch (sco_conn->state) {
|
|
case BT_CONN_INITIATING:
|
|
case BT_CONN_CONNECTED:
|
|
return sco_conn;
|
|
default:
|
|
bt_conn_unref(sco_conn);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) {
|
|
link_type = BT_HCI_ESCO;
|
|
} else {
|
|
link_type = BT_HCI_SCO;
|
|
}
|
|
|
|
sco_conn = bt_conn_add_sco(peer, link_type);
|
|
if (!sco_conn) {
|
|
return NULL;
|
|
}
|
|
|
|
sco_conn->sco.link_type = link_type;
|
|
|
|
bt_sco_chan_add(sco_conn, chan);
|
|
bt_conn_set_state(chan->sco, BT_CONN_INITIATING);
|
|
bt_sco_chan_set_state(chan, BT_SCO_STATE_CONNECTING);
|
|
|
|
if (sco_setup_sync_conn(sco_conn) < 0) {
|
|
bt_conn_set_state(chan->sco, BT_CONN_DISCONNECTED);
|
|
bt_sco_chan_set_state(chan, BT_SCO_STATE_DISCONNECTED);
|
|
bt_sco_cleanup(sco_conn);
|
|
return NULL;
|
|
}
|
|
|
|
return sco_conn;
|
|
}
|