Channel direction was actually a source of confusion since the spec does actually define the data path direction from controller point of view meaning input actually means transmit and output receive, also direction can be configured with different QoS settings. To make these APIs less confusing and allow QoS to be configured independently they are now split into RX (receiving) and TX (transmission) settings including its data path. Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
507 lines
11 KiB
C
507 lines
11 KiB
C
/** @file
|
|
* @brief Bluetooth Audio shell
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2020 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <zephyr.h>
|
|
#include <shell/shell.h>
|
|
#include <sys/printk.h>
|
|
#include <sys/byteorder.h>
|
|
#include <sys/util.h>
|
|
|
|
#include <bluetooth/hci.h>
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/conn.h>
|
|
#include <bluetooth/iso.h>
|
|
|
|
#include "bt.h"
|
|
|
|
static void iso_recv(struct bt_iso_chan *chan, struct net_buf *buf)
|
|
{
|
|
printk("Incoming data channel %p len %u\n", chan, buf->len);
|
|
}
|
|
|
|
static void iso_connected(struct bt_iso_chan *chan)
|
|
{
|
|
printk("ISO Channel %p connected\n", chan);
|
|
}
|
|
|
|
static void iso_disconnected(struct bt_iso_chan *chan)
|
|
{
|
|
printk("ISO Channel %p disconnected\n", chan);
|
|
}
|
|
|
|
static struct bt_iso_chan_ops iso_ops = {
|
|
.recv = iso_recv,
|
|
.connected = iso_connected,
|
|
.disconnected = iso_disconnected,
|
|
};
|
|
|
|
#define DEFAULT_IO_QOS \
|
|
{ \
|
|
.interval = 10000u, \
|
|
.latency = 10u, \
|
|
.sdu = 40u, \
|
|
.phy = BT_GAP_LE_PHY_2M, \
|
|
.rtn = 2u, \
|
|
}
|
|
|
|
static struct bt_iso_chan_io_qos iso_tx_qos = DEFAULT_IO_QOS;
|
|
static struct bt_iso_chan_io_qos iso_rx_qos = DEFAULT_IO_QOS;
|
|
|
|
static struct bt_iso_chan_qos iso_qos = {
|
|
.sca = BT_GAP_SCA_UNKNOWN,
|
|
.tx = &iso_tx_qos,
|
|
.rx = &iso_rx_qos,
|
|
};
|
|
|
|
struct bt_iso_chan iso_chan = {
|
|
.ops = &iso_ops,
|
|
.qos = &iso_qos,
|
|
};
|
|
|
|
static int iso_accept(struct bt_conn *conn, struct bt_iso_chan **chan)
|
|
{
|
|
printk("Incoming conn %p\n", conn);
|
|
|
|
if (iso_chan.conn) {
|
|
printk("No channels available\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
*chan = &iso_chan;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct bt_iso_server iso_server = {
|
|
.sec_level = BT_SECURITY_L1,
|
|
.accept = iso_accept,
|
|
};
|
|
|
|
static int cmd_listen(const struct shell *shell, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
|
|
if (argc > 1) {
|
|
iso_server.sec_level = *argv[1] - '0';
|
|
}
|
|
|
|
err = bt_iso_server_register(&iso_server);
|
|
if (err) {
|
|
shell_error(shell, "Unable to register ISO cap (err %d)",
|
|
err);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cmd_bind(const struct shell *shell, size_t argc, char *argv[])
|
|
{
|
|
struct bt_conn *conns[1];
|
|
struct bt_iso_chan *chans[1];
|
|
int err;
|
|
|
|
if (!default_conn) {
|
|
shell_error(shell, "Not connected");
|
|
return 0;
|
|
}
|
|
|
|
if (iso_chan.conn) {
|
|
shell_error(shell, "Already bound");
|
|
return 0;
|
|
}
|
|
|
|
conns[0] = default_conn;
|
|
chans[0] = &iso_chan;
|
|
|
|
if (argc > 1) {
|
|
if (!strcmp("tx", argv[2])) {
|
|
chans[0]->qos->tx = &iso_tx_qos;
|
|
chans[0]->qos->rx = NULL;
|
|
} else if (!strcmp("rx", argv[2])) {
|
|
chans[0]->qos->tx = NULL;
|
|
chans[0]->qos->rx = &iso_rx_qos;
|
|
} else if (!strcmp("txrx", argv[2])) {
|
|
chans[0]->qos->tx = &iso_tx_qos;
|
|
chans[0]->qos->rx = &iso_rx_qos;
|
|
}
|
|
}
|
|
|
|
if (argc > 2) {
|
|
if (chans[0]->qos->tx) {
|
|
chans[0]->qos->tx->interval = strtol(argv[2], NULL, 0);
|
|
}
|
|
|
|
if (chans[0]->qos->rx) {
|
|
chans[0]->qos->rx->interval = strtol(argv[2], NULL, 0);
|
|
}
|
|
}
|
|
|
|
if (argc > 3) {
|
|
chans[0]->qos->packing = strtol(argv[3], NULL, 0);
|
|
}
|
|
|
|
if (argc > 4) {
|
|
chans[0]->qos->framing = strtol(argv[4], NULL, 0);
|
|
}
|
|
|
|
if (argc > 5) {
|
|
if (chans[0]->qos->tx) {
|
|
chans[0]->qos->tx->latency = strtol(argv[5], NULL, 0);
|
|
}
|
|
|
|
if (chans[0]->qos->rx) {
|
|
chans[0]->qos->rx->latency = strtol(argv[5], NULL, 0);
|
|
}
|
|
}
|
|
|
|
if (argc > 6) {
|
|
if (chans[0]->qos->tx) {
|
|
chans[0]->qos->tx->sdu = strtol(argv[6], NULL, 0);
|
|
}
|
|
|
|
if (chans[0]->qos->rx) {
|
|
chans[0]->qos->rx->sdu = strtol(argv[6], NULL, 0);
|
|
}
|
|
}
|
|
|
|
if (argc > 7) {
|
|
if (chans[0]->qos->tx) {
|
|
chans[0]->qos->tx->phy = strtol(argv[7], NULL, 0);
|
|
}
|
|
|
|
if (chans[0]->qos->rx) {
|
|
chans[0]->qos->rx->phy = strtol(argv[7], NULL, 0);
|
|
}
|
|
}
|
|
|
|
if (argc > 8) {
|
|
if (chans[0]->qos->tx) {
|
|
chans[0]->qos->tx->rtn = strtol(argv[8], NULL, 0);
|
|
}
|
|
|
|
if (chans[0]->qos->rx) {
|
|
chans[0]->qos->rx->rtn = strtol(argv[8], NULL, 0);
|
|
}
|
|
}
|
|
|
|
err = bt_iso_chan_bind(conns, 1, chans);
|
|
if (err) {
|
|
shell_error(shell, "Unable to bind (err %d)", err);
|
|
return 0;
|
|
}
|
|
|
|
shell_print(shell, "ISO Channel bound");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_connect(const struct shell *shell, size_t argc, char *argv[])
|
|
{
|
|
struct bt_iso_chan *chans[1];
|
|
int err;
|
|
|
|
if (!iso_chan.conn) {
|
|
shell_error(shell, "Not bound");
|
|
return 0;
|
|
}
|
|
|
|
chans[0] = &iso_chan;
|
|
|
|
err = bt_iso_chan_connect(chans, 1);
|
|
if (err) {
|
|
shell_error(shell, "Unable to connect (err %d)", err);
|
|
return 0;
|
|
}
|
|
|
|
shell_print(shell, "ISO Connect pending...");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define DATA_MTU CONFIG_BT_ISO_TX_MTU
|
|
NET_BUF_POOL_FIXED_DEFINE(tx_pool, 1, DATA_MTU, NULL);
|
|
|
|
static int cmd_send(const struct shell *shell, size_t argc, char *argv[])
|
|
{
|
|
static uint8_t buf_data[DATA_MTU] = { [0 ... (DATA_MTU - 1)] = 0xff };
|
|
int ret, len, count = 1;
|
|
struct net_buf *buf;
|
|
|
|
if (argc > 1) {
|
|
count = strtoul(argv[1], NULL, 10);
|
|
}
|
|
|
|
if (!iso_chan.conn) {
|
|
shell_error(shell, "Not bound");
|
|
return 0;
|
|
}
|
|
|
|
if (!iso_chan.qos->tx) {
|
|
shell_error(shell, "Transmission QoS disabled");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
len = MIN(iso_chan.qos->tx->sdu, DATA_MTU - BT_ISO_CHAN_SEND_RESERVE);
|
|
|
|
while (count--) {
|
|
buf = net_buf_alloc(&tx_pool, K_FOREVER);
|
|
net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
|
|
|
|
net_buf_add_mem(buf, buf_data, len);
|
|
ret = bt_iso_chan_send(&iso_chan, buf);
|
|
if (ret < 0) {
|
|
shell_print(shell, "Unable to send: %d", -ret);
|
|
net_buf_unref(buf);
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
|
|
shell_print(shell, "ISO sending...");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_disconnect(const struct shell *shell, size_t argc,
|
|
char *argv[])
|
|
{
|
|
int err;
|
|
|
|
err = bt_iso_chan_disconnect(&iso_chan);
|
|
if (err) {
|
|
shell_error(shell, "Unable to disconnect (err %d)", err);
|
|
return 0;
|
|
}
|
|
|
|
shell_print(shell, "ISO Disconnect pending...");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_ISO_BROADCAST)
|
|
#define BIS_ISO_CHAN_COUNT 1
|
|
|
|
static struct bt_iso_chan_qos bis_iso_qos;
|
|
|
|
static struct bt_iso_chan bis_iso_chan = {
|
|
.ops = &iso_ops,
|
|
.qos = &bis_iso_qos,
|
|
};
|
|
|
|
static struct bt_iso_chan *bis_channels[BIS_ISO_CHAN_COUNT] = { &bis_iso_chan };
|
|
|
|
static struct bt_iso_big *big;
|
|
|
|
NET_BUF_POOL_FIXED_DEFINE(bis_tx_pool, BIS_ISO_CHAN_COUNT, DATA_MTU, NULL);
|
|
|
|
static int cmd_broadcast(const struct shell *shell, size_t argc, char *argv[])
|
|
{
|
|
static uint8_t buf_data[DATA_MTU] = { [0 ... (DATA_MTU - 1)] = 0xff };
|
|
int ret, len, count = 1;
|
|
struct net_buf *buf;
|
|
|
|
if (argc > 1) {
|
|
count = strtoul(argv[1], NULL, 10);
|
|
}
|
|
|
|
if (!bis_iso_chan.conn) {
|
|
shell_error(shell, "BIG not created");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
if (!bis_iso_qos.tx) {
|
|
shell_error(shell, "BIG not setup as broadcaster");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
len = MIN(iso_chan.qos->tx->sdu, DATA_MTU - BT_ISO_CHAN_SEND_RESERVE);
|
|
|
|
while (count--) {
|
|
for (int i = 0; i < BIS_ISO_CHAN_COUNT; i++) {
|
|
buf = net_buf_alloc(&tx_pool, K_FOREVER);
|
|
net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);
|
|
|
|
net_buf_add_mem(buf, buf_data, len);
|
|
ret = bt_iso_chan_send(&bis_iso_chan, buf);
|
|
if (ret < 0) {
|
|
shell_print(shell, "[%i]: Unable to broadcast: %d", i, -ret);
|
|
net_buf_unref(buf);
|
|
return -ENOEXEC;
|
|
}
|
|
}
|
|
}
|
|
|
|
shell_print(shell, "ISO broadcasting...");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_big_create(const struct shell *shell, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
struct bt_iso_big_create_param param;
|
|
struct bt_le_ext_adv *adv = adv_sets[selected_adv];
|
|
|
|
if (!adv) {
|
|
shell_error(shell, "No (periodic) advertising set selected");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
/* TODO: Allow setting QOS from shell */
|
|
bis_iso_qos.tx = &iso_tx_qos;
|
|
bis_iso_qos.tx->interval = 10000; /* us */
|
|
bis_iso_qos.tx->latency = 20; /* ms */
|
|
bis_iso_qos.tx->phy = BT_GAP_LE_PHY_2M; /* 2 MBit */
|
|
bis_iso_qos.tx->rtn = 2;
|
|
bis_iso_qos.tx->sdu = CONFIG_BT_ISO_TX_MTU;
|
|
|
|
param.bis_channels = bis_channels;
|
|
param.num_bis = BIS_ISO_CHAN_COUNT;
|
|
param.encryption = false;
|
|
|
|
if (argc > 1) {
|
|
if (!strcmp(argv[1], "enc")) {
|
|
uint8_t bcode_len = hex2bin(argv[1], strlen(argv[1]), param.bcode,
|
|
sizeof(param.bcode));
|
|
if (!bcode_len || bcode_len != sizeof(param.bcode)) {
|
|
shell_error(shell, "Invalid Broadcast Code Length");
|
|
return -ENOEXEC;
|
|
}
|
|
param.encryption = true;
|
|
} else {
|
|
shell_help(shell);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
}
|
|
|
|
err = bt_iso_big_create(adv, ¶m, &big);
|
|
if (err) {
|
|
shell_error(shell, "Unable to create BIG (err %d)", err);
|
|
return 0;
|
|
}
|
|
|
|
shell_print(shell, "BIG created");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_big_sync(const struct shell *shell, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
/* TODO: Add support to select which PA sync to BIG sync to */
|
|
struct bt_le_per_adv_sync *pa_sync = per_adv_syncs[0];
|
|
struct bt_iso_big_sync_param param;
|
|
|
|
if (!pa_sync) {
|
|
shell_error(shell, "No PA sync selected");
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
bis_iso_qos.tx = NULL;
|
|
|
|
param.bis_channels = bis_channels;
|
|
param.num_bis = BIS_ISO_CHAN_COUNT;
|
|
param.encryption = false;
|
|
param.bis_bitfield = strtoul(argv[1], NULL, 16);
|
|
param.mse = 0;
|
|
param.sync_timeout = 0xFF;
|
|
|
|
for (int i = 2; i < argc; i++) {
|
|
if (!strcmp(argv[i], "mse")) {
|
|
param.mse = strtoul(argv[i], NULL, 16);
|
|
} else if (!strcmp(argv[i], "timeout")) {
|
|
param.sync_timeout = strtoul(argv[i], NULL, 16);
|
|
} else if (!strcmp(argv[i], "enc")) {
|
|
uint8_t bcode_len;
|
|
|
|
i++;
|
|
if (i == argc) {
|
|
shell_help(shell);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
|
|
bcode_len = hex2bin(argv[i], strlen(argv[i]), param.bcode,
|
|
sizeof(param.bcode));
|
|
|
|
if (!bcode_len || bcode_len != sizeof(param.bcode)) {
|
|
shell_error(shell, "Invalid Broadcast Code Length");
|
|
return -ENOEXEC;
|
|
}
|
|
param.encryption = true;
|
|
} else {
|
|
shell_help(shell);
|
|
return SHELL_CMD_HELP_PRINTED;
|
|
}
|
|
}
|
|
|
|
err = bt_iso_big_sync(pa_sync, ¶m, &big);
|
|
if (err) {
|
|
shell_error(shell, "Unable to sync to BIG (err %d)", err);
|
|
return 0;
|
|
}
|
|
|
|
shell_print(shell, "BIG syncing");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_big_term(const struct shell *shell, size_t argc, char *argv[])
|
|
{
|
|
int err;
|
|
|
|
err = bt_iso_big_terminate(big);
|
|
if (err) {
|
|
shell_error(shell, "Unable to terminate BIG", err);
|
|
return 0;
|
|
}
|
|
|
|
shell_print(shell, "BIG terminated");
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_BT_ISO_BROADCAST */
|
|
|
|
SHELL_STATIC_SUBCMD_SET_CREATE(iso_cmds,
|
|
SHELL_CMD_ARG(bind, NULL, "[dir=tx,rx,txrx] [interval] [packing] [framing] "
|
|
"[latency] [sdu] [phy] [rtn]", cmd_bind, 1, 8),
|
|
SHELL_CMD_ARG(connect, NULL, "Connect ISO Channel", cmd_connect, 1, 0),
|
|
SHELL_CMD_ARG(listen, NULL, "[security level]", cmd_listen, 1, 1),
|
|
SHELL_CMD_ARG(send, NULL, "Send to ISO Channel [count]",
|
|
cmd_send, 1, 1),
|
|
SHELL_CMD_ARG(disconnect, NULL, "Disconnect ISO Channel",
|
|
cmd_disconnect, 1, 0),
|
|
#if defined(CONFIG_BT_ISO_BROADCAST)
|
|
SHELL_CMD_ARG(create-big, NULL, "Create a BIG as a broadcaster [enc <broadcast code>]",
|
|
cmd_big_create, 1, 2),
|
|
SHELL_CMD_ARG(sync-big, NULL, "Synchronize to a BIG as a receiver <BIS bitfield> [mse] "
|
|
"[timeout] [enc <broadcast code>]", cmd_big_sync, 2, 4),
|
|
SHELL_CMD_ARG(term-big, NULL, "Terminate a BIG", cmd_big_term, 1, 0),
|
|
SHELL_CMD_ARG(broadcast, NULL, "Broadcast on ISO channels", cmd_broadcast, 1, 1),
|
|
#endif /* CONFIG_BT_ISO_BROADCAST */
|
|
SHELL_SUBCMD_SET_END
|
|
);
|
|
|
|
static int cmd_iso(const struct shell *shell, size_t argc, char **argv)
|
|
{
|
|
if (argc > 1) {
|
|
shell_error(shell, "%s unknown parameter: %s",
|
|
argv[0], argv[1]);
|
|
} else {
|
|
shell_error(shell, "%s Missing subcommand", argv[0]);
|
|
}
|
|
|
|
return -ENOEXEC;
|
|
}
|
|
|
|
SHELL_CMD_ARG_REGISTER(iso, &iso_cmds, "Bluetooth ISO shell commands",
|
|
cmd_iso, 1, 1);
|