blob: 95054184455ca952f2f5b672f7f33db8190e3aea [file] [log] [blame]
/* 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_CONNECTING);
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_CONNECTING:
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_CONNECTING);
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;
}