Commit c476456c authored by Emil Gydesen's avatar Emil Gydesen Committed by Johan Hedberg
Browse files

Tests: Bluetooth: Add BSIM test of ISO BIS

Adds 2 tests for ISO broadcast.

First test is just a minimal test that sets up a BIG,
transmits data and verifies that it is being received
on the ISO sync receiver.

The second test creates a BIG, disables BT, enables BT,
and the verifies the above.

Signed-off-by: default avatarEmil Gydesen <emil.gydesen@nordicsemi.no>
parent 4a977463
Loading
Loading
Loading
Loading
+1 −0
Changes for tests/bsim/bluetooth/host/compile.sh: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ ${ZEPHYR_BASE}/tests/bsim/bluetooth/host/l2cap/compile.sh
${ZEPHYR_BASE}/tests/bsim/bluetooth/host/security/compile.sh

app=tests/bsim/bluetooth/host/iso/cis compile
app=tests/bsim/bluetooth/host/iso/bis compile

app=tests/bsim/bluetooth/host/misc/disable compile
app=tests/bsim/bluetooth/host/misc/disconnect/dut compile
+22 −0
Changes for tests/bsim/bluetooth/host/iso/bis/CMakeLists.txt: 22 added lines, 0 removed lines.
Original line number Diff line number Diff line
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

project(bsim_test_iso_bis)

add_subdirectory(${ZEPHYR_BASE}/tests/bsim/babblekit babblekit)
target_link_libraries(app PRIVATE babblekit)

target_sources(app PRIVATE
  src/common.c
  src/bis_broadcaster.c
  src/bis_receiver.c
  src/main.c
)

zephyr_include_directories(
  ${BSIM_COMPONENTS_PATH}/libUtilv1/src/
  ${BSIM_COMPONENTS_PATH}/libPhyComv1/src/
)
+27 −0
Changes for tests/bsim/bluetooth/host/iso/bis/prj.conf: 27 added lines, 0 removed lines.
Original line number Diff line number Diff line
CONFIG_BT=y
CONFIG_LOG=y
CONFIG_ASSERT=y
CONFIG_BT_EXT_ADV=y
CONFIG_BT_PER_ADV=y
CONFIG_BT_PER_ADV_SYNC=y

CONFIG_BT_DEVICE_NAME="BIS test"

CONFIG_BT_ISO_BROADCASTER=y
CONFIG_BT_ISO_SYNC_RECEIVER=y
CONFIG_BT_ISO_TX_BUF_COUNT=4
CONFIG_BT_ISO_MAX_CHAN=4
CONFIG_BT_ISO_TX_MTU=200
CONFIG_BT_ISO_RX_MTU=200

CONFIG_BT_ISO_LOG_LEVEL_DBG=y

# Controller ISO configs
CONFIG_BT_CTLR_ADV_ISO=y
CONFIG_BT_CTLR_ADV_ISO_STREAM_MAX=4
CONFIG_BT_CTLR_SYNC_ISO=y
CONFIG_BT_CTLR_SYNC_ISO_STREAM_MAX=4
CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=208
CONFIG_BT_CTLR_ISO_TX_BUFFERS=4
CONFIG_BT_CTLR_ISOAL_SOURCES=4
CONFIG_BT_CTLR_ISOAL_SINKS=4
+328 −0
Changes for tests/bsim/bluetooth/host/iso/bis/src/bis_broadcaster.c: 328 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*
 * Copyright (c) 2024 Nordic Semiconductor
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "common.h"

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/logging/log.h>

#include "babblekit/flags.h"
#include "babblekit/sync.h"
#include "babblekit/testcase.h"

LOG_MODULE_REGISTER(bis_broadcaster, LOG_LEVEL_INF);

#define LATENCY_MS      10U                 /* 10ms */
#define SDU_INTERVAL_US 10U * USEC_PER_MSEC /* 10 ms */

extern enum bst_result_t bst_result;
static struct bt_iso_chan iso_chans[CONFIG_BT_ISO_MAX_CHAN];
static struct bt_iso_chan *default_chan = &iso_chans[0];
static uint16_t seq_num;
NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_ISO_TX_BUF_COUNT,
			  BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU),
			  CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL);

static DEFINE_FLAG(flag_iso_connected);

static void send_data_cb(struct k_work *work);
K_WORK_DELAYABLE_DEFINE(iso_send_work, send_data_cb);

static void send_data(struct bt_iso_chan *chan)
{
	static uint8_t buf_data[CONFIG_BT_ISO_TX_MTU];
	static size_t len_to_send = 1U;
	static bool data_initialized;
	struct net_buf *buf;
	int ret;

	if (!IS_FLAG_SET(flag_iso_connected)) {
		/* TX has been aborted */
		return;
	}

	if (!data_initialized) {
		for (int i = 0; i < ARRAY_SIZE(buf_data); i++) {
			buf_data[i] = (uint8_t)i;
		}

		data_initialized = true;
	}

	buf = net_buf_alloc(&tx_pool, K_NO_WAIT);
	TEST_ASSERT(buf != NULL, "Failed to allocate buffer");

	net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE);

	net_buf_add_mem(buf, buf_data, len_to_send);

	ret = bt_iso_chan_send(default_chan, buf, seq_num++);
	if (ret < 0) {
		LOG_DBG("Failed to send ISO data: %d", ret);
		net_buf_unref(buf);

		/* Reschedule for next interval */
		k_work_reschedule(&iso_send_work, K_USEC(SDU_INTERVAL_US));

		return;
	}

	len_to_send++;
	if (len_to_send > ARRAY_SIZE(buf_data)) {
		len_to_send = 1;
	}
}

static void send_data_cb(struct k_work *work)
{
	const uint16_t tx_pool_cnt = tx_pool.uninit_count;

	/* Send/enqueue as many as we can */
	for (uint16_t i = 0U; i < tx_pool_cnt; i++) {
		send_data(default_chan);
	}
}

static void iso_connected_cb(struct bt_iso_chan *chan)
{
	LOG_INF("ISO Channel %p connected", chan);

	if (chan == default_chan) {
		seq_num = 0U;

		SET_FLAG(flag_iso_connected);
	}
}

static void iso_disconnected_cb(struct bt_iso_chan *chan, uint8_t reason)
{
	LOG_INF("ISO Channel %p disconnected (reason 0x%02x)", chan, reason);

	if (chan == default_chan) {
		k_work_cancel_delayable(&iso_send_work);

		UNSET_FLAG(flag_iso_connected);
	}
}

static void sdu_sent_cb(struct bt_iso_chan *chan)
{
	if (!IS_FLAG_SET(flag_iso_connected)) {
		/* TX has been aborted */
		return;
	}

	send_data(chan);
}

static void init(void)
{
	static struct bt_iso_chan_ops iso_ops = {
		.disconnected = iso_disconnected_cb,
		.connected = iso_connected_cb,
		.sent = sdu_sent_cb,
	};
	static struct bt_iso_chan_io_qos iso_tx = {
		.sdu = CONFIG_BT_ISO_TX_MTU,
		.phy = BT_GAP_LE_PHY_2M,
		.rtn = 1,
		.path = NULL,
	};
	static struct bt_iso_chan_qos iso_qos = {
		.tx = &iso_tx,
		.rx = NULL,
	};
	int err;

	err = bt_enable(NULL);
	TEST_ASSERT(err == 0, "Bluetooth enable failed: %d", err);

	for (size_t i = 0U; i < ARRAY_SIZE(iso_chans); i++) {
		iso_chans[i].ops = &iso_ops;
		iso_chans[i].qos = &iso_qos;
	}

	bk_sync_init();
}

static void create_ext_adv(struct bt_le_ext_adv **adv)
{
	int err;

	LOG_INF("Creating extended advertising set with periodic advertising");

	/* Create a non-connectable non-scannable advertising set */
	err = bt_le_ext_adv_create(BT_LE_EXT_ADV_NCONN, NULL, adv);
	TEST_ASSERT(err == 0, "Unable to create extended advertising set: %d", err);

	/* Set periodic advertising parameters */
	err = bt_le_per_adv_set_param(*adv, BT_LE_PER_ADV_PARAM(BT_GAP_PER_ADV_FAST_INT_MIN_2,
								BT_GAP_PER_ADV_FAST_INT_MAX_2,
								BT_LE_PER_ADV_OPT_NONE));
	TEST_ASSERT(err == 0, "Failed to set periodic advertising parameters: %d", err);
}

static void start_ext_adv(struct bt_le_ext_adv *adv)
{
	int err;

	LOG_INF("Starting extended and periodic advertising");

	/* Start extended advertising */
	err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT);
	TEST_ASSERT(err == 0, "Failed to start extended advertising: %d", err);

	/* FIXME: Temporary workaround to get around an assert in the controller
	 * Open issue: https://github.com/zephyrproject-rtos/zephyr/issues/72852
	 */
	k_sleep(K_MSEC(100));

	/* Enable Periodic Advertising */
	err = bt_le_per_adv_start(adv);
	TEST_ASSERT(err == 0, "Failed to enable periodic advertising: %d", err);
}

static void create_big(struct bt_le_ext_adv *adv, size_t cnt, struct bt_iso_big **out_big)
{
	struct bt_iso_chan *channels[ARRAY_SIZE(iso_chans)];
	struct bt_iso_big_create_param param = {
		.packing = BT_ISO_PACKING_SEQUENTIAL,
		.framing = BT_ISO_FRAMING_UNFRAMED,
		.interval = SDU_INTERVAL_US,
		.bis_channels = channels,
		.latency = LATENCY_MS,
		.encryption = false,
		.num_bis = cnt,
	};
	int err;

	for (size_t i = 0U; i < cnt; i++) {
		channels[i] = &iso_chans[i];
	}

	LOG_INF("Creating BIG");

	err = bt_iso_big_create(adv, &param, out_big);
	TEST_ASSERT(err == 0, "Failed to create BIG: %d", err);

	WAIT_FOR_FLAG(flag_iso_connected);
}

static void start_tx(void)
{
	const uint16_t tx_pool_cnt = tx_pool.uninit_count;

	LOG_INF("Starting TX");

	/* Send/enqueue as many as we can */
	for (uint16_t i = 0U; i < tx_pool_cnt; i++) {
		send_data(default_chan);
	}
}

static void terminate_big(struct bt_iso_big *big)
{
	int err;

	LOG_INF("Terminating BIG");

	err = bt_iso_big_terminate(big);
	TEST_ASSERT(err == 0, "Failed to terminate BIG: %d", err);

	big = NULL;
}

static void reset_bluetooth(void)
{
	int err;

	LOG_INF("Resetting Bluetooth");

	err = bt_disable();
	TEST_ASSERT(err == 0, "Failed to disable: %d", err);

	err = bt_enable(NULL);
	TEST_ASSERT(err == 0, "Failed to re-enable: %d", err);
}

static void test_main(void)
{
	struct bt_le_ext_adv *adv;
	struct bt_iso_big *big;

	init();

	/* Create advertising set and BIG and start it and starting TXing */
	create_ext_adv(&adv);
	create_big(adv, 1U, &big);
	start_ext_adv(adv);
	start_tx();

	/* Wait for receiver to tell us to terminate */
	bk_sync_wait();

	terminate_big(big);
	big = NULL;

	TEST_PASS("Test passed");
}

static void test_main_disable(void)
{
	struct bt_le_ext_adv *adv;
	struct bt_iso_big *big;

	init();

	/* Create advertising set and BIG */
	create_ext_adv(&adv);
	create_big(adv, ARRAY_SIZE(iso_chans), &big);

	/* Reset BT to see if we can set it up again */
	reset_bluetooth();

	/* After a disable, all advertising sets and BIGs are removed */
	big = NULL;
	adv = NULL;

	/* Set everything up again to see if everything still works as expected */
	create_ext_adv(&adv);
	create_big(adv, ARRAY_SIZE(iso_chans), &big);
	start_ext_adv(adv);
	start_tx();

	/* Wait for receiver to tell us to terminate */
	bk_sync_wait();

	terminate_big(big);
	big = NULL;

	TEST_PASS("Disable test passed");
}

static const struct bst_test_instance test_def[] = {
	{
		.test_id = "broadcaster",
		.test_descr = "Minimal BIS broadcaster that broadcast ISO data",
		.test_post_init_f = test_init,
		.test_tick_f = test_tick,
		.test_main_f = test_main,
	},
	{
		.test_id = "broadcaster_disable",
		.test_descr = "BIS broadcaster that tests bt_disable for ISO",
		.test_post_init_f = test_init,
		.test_tick_f = test_tick,
		.test_main_f = test_main_disable,
	},
	BSTEST_END_MARKER,
};

struct bst_test_list *test_main_bis_broadcaster_install(struct bst_test_list *tests)
{
	return bst_add_tests(tests, test_def);
}
+280 −0
Changes for tests/bsim/bluetooth/host/iso/bis/src/bis_receiver.c: 280 added lines, 0 removed lines.
Original line number Diff line number Diff line
/*
 * Copyright (c) 2024 Nordic Semiconductor
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "common.h"

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/logging/log.h>

#include "babblekit/flags.h"
#include "babblekit/sync.h"
#include "babblekit/testcase.h"

LOG_MODULE_REGISTER(bis_receiver, LOG_LEVEL_INF);

#define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 5U /* Set the timeout relative to interval */

extern enum bst_result_t bst_result;

static DEFINE_FLAG(flag_broadcaster_found);
static DEFINE_FLAG(flag_iso_connected);
static DEFINE_FLAG(flag_data_received);
static DEFINE_FLAG(flag_pa_synced);
static DEFINE_FLAG(flag_biginfo);

static struct bt_iso_chan iso_chans[CONFIG_BT_ISO_MAX_CHAN];
static struct bt_le_scan_recv_info broadcaster_info;
static bt_addr_le_t broadcaster_addr;
static uint8_t broadcaster_num_bis;

/** Log data as d_0 d_1 d_2 ... d_(n-2) d_(n-1) d_(n) to show the 3 first and 3 last octets
 *
 * Examples:
 * 01
 * 0102
 * 010203
 * 01020304
 * 0102030405
 * 010203040506
 * 010203...050607
 * 010203...060708
 * etc.
 */
static void iso_log_data(uint8_t *data, size_t data_len)
{
	/* Maximum number of octets from each end of the data */
	const uint8_t max_octets = 3;
	char data_str[35];
	size_t str_len;

	str_len = bin2hex(data, MIN(max_octets, data_len), data_str, sizeof(data_str));
	if (data_len > max_octets) {
		if (data_len > (max_octets * 2)) {
			static const char dots[] = "...";

			strcat(&data_str[str_len], dots);
			str_len += strlen(dots);
		}

		str_len += bin2hex(data + (data_len - MIN(max_octets, data_len - max_octets)),
				   MIN(max_octets, data_len - max_octets), data_str + str_len,
				   sizeof(data_str) - str_len);
	}

	LOG_DBG("\t %s", data_str);
}

static void iso_recv(struct bt_iso_chan *chan, const struct bt_iso_recv_info *info,
		     struct net_buf *buf)
{
	if (info->flags & BT_ISO_FLAGS_VALID) {
		LOG_DBG("Incoming data channel %p len %u", chan, buf->len);
		iso_log_data(buf->data, buf->len);
		SET_FLAG(flag_data_received);
	}
}

static void iso_connected(struct bt_iso_chan *chan)
{
	LOG_INF("ISO Channel %p connected", chan);

	SET_FLAG(flag_iso_connected);
}

static void iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
{
	LOG_INF("ISO Channel %p disconnected (reason 0x%02x)", chan, reason);

	UNSET_FLAG(flag_iso_connected);
}

static void broadcast_scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *ad)
{
	if (IS_FLAG_SET(flag_broadcaster_found)) {
		/* no-op*/
		return;
	}

	LOG_INF("Broadcaster found");

	if (info->interval != 0U) {
		memcpy(&broadcaster_info, info, sizeof(broadcaster_info));
		bt_addr_le_copy(&broadcaster_addr, info->addr);
		SET_FLAG(flag_broadcaster_found);
	}
}

static void pa_synced_cb(struct bt_le_per_adv_sync *sync,
			 struct bt_le_per_adv_sync_synced_info *info)
{
	LOG_INF("PA synced");

	SET_FLAG(flag_pa_synced);
}

static void pa_term_cb(struct bt_le_per_adv_sync *sync,
		       const struct bt_le_per_adv_sync_term_info *info)
{
	LOG_INF("PA terminated");

	UNSET_FLAG(flag_pa_synced);
}

static void pa_biginfo_cb(struct bt_le_per_adv_sync *sync, const struct bt_iso_biginfo *biginfo)
{
	if (IS_FLAG_SET(flag_biginfo)) {
		/* no-op*/
		return;
	}

	LOG_INF("BIGInfo received");

	broadcaster_num_bis = biginfo->num_bis;
	SET_FLAG(flag_biginfo);
}

static void init(void)
{
	static struct bt_le_per_adv_sync_cb pa_sync_cbs = {
		.biginfo = pa_biginfo_cb,
		.synced = pa_synced_cb,
		.term = pa_term_cb,
	};
	static struct bt_le_scan_cb bap_scan_cb = {
		.recv = broadcast_scan_recv,
	};
	static struct bt_iso_chan_io_qos iso_rx = {
		.sdu = CONFIG_BT_ISO_TX_MTU,
	};
	static struct bt_iso_chan_ops iso_ops = {
		.recv = iso_recv,
		.connected = iso_connected,
		.disconnected = iso_disconnected,
	};
	static struct bt_iso_chan_qos iso_qos = {
		.rx = &iso_rx,
	};
	int err;

	err = bt_enable(NULL);
	TEST_ASSERT(err == 0, "Bluetooth enable failed (err %d)", err);

	for (size_t i = 0U; i < ARRAY_SIZE(iso_chans); i++) {
		iso_chans[i].ops = &iso_ops;
		iso_chans[i].qos = &iso_qos;
	}

	bt_le_per_adv_sync_cb_register(&pa_sync_cbs);
	bt_le_scan_cb_register(&bap_scan_cb);

	bk_sync_init();
}

static uint16_t interval_to_sync_timeout(uint16_t pa_interval)
{
	uint32_t interval_ms;
	uint16_t pa_timeout;
	uint32_t timeout;

	/* Add retries and convert to unit in 10's of ms */
	interval_ms = BT_GAP_PER_ADV_INTERVAL_TO_MS(pa_interval);
	timeout = (interval_ms * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO) / 10U;

	/* Enforce restraints */
	pa_timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, BT_GAP_PER_ADV_MAX_TIMEOUT);

	return pa_timeout;
}

static void scan_and_sync_pa(struct bt_le_per_adv_sync **out_sync)
{
	struct bt_le_per_adv_sync_param create_params = {0};
	int err;

	LOG_INF("Starting scan");
	err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL);
	TEST_ASSERT(err == 0, "Failed to start scan: %d", err);

	WAIT_FOR_FLAG(flag_broadcaster_found);

	bt_addr_le_copy(&create_params.addr, &broadcaster_addr);
	create_params.options = BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE;
	create_params.sid = broadcaster_info.sid;
	create_params.skip = 0U;
	create_params.timeout = interval_to_sync_timeout(broadcaster_info.interval);

	LOG_INF("Creating PA sync");
	err = bt_le_per_adv_sync_create(&create_params, out_sync);
	TEST_ASSERT(err == 0, "Failed to sync to PA: %d", err);

	WAIT_FOR_FLAG(flag_pa_synced);

	LOG_INF("Stopping scan");
	err = bt_le_scan_stop();
	TEST_ASSERT(err == 0, "Failed to stop scan: %d", err);
}

static void sync_big(struct bt_le_per_adv_sync *sync, uint8_t cnt, struct bt_iso_big **out_big)
{
	struct bt_iso_chan *bis_channels[CONFIG_BT_ISO_MAX_CHAN];
	struct bt_iso_big_sync_param param = {
		.sync_timeout = interval_to_sync_timeout(broadcaster_info.interval),
		.bis_bitfield = BIT_MASK(cnt) << 1U, /* BIS indexes start from 1, thus shift by 1 */
		.bis_channels = bis_channels,
		.mse = BT_ISO_SYNC_MSE_MIN,
		.encryption = false,
		.num_bis = cnt,
	};
	int err;

	TEST_ASSERT(cnt <= ARRAY_SIZE(bis_channels));
	for (size_t i = 0U; i < cnt; i++) {
		bis_channels[i] = &iso_chans[i];
	}

	LOG_INF("Creating BIG sync");
	err = bt_iso_big_sync(sync, &param, out_big);
	TEST_ASSERT(err == 0, "Failed to create BIG sync: %d");

	WAIT_FOR_FLAG(flag_iso_connected);
}

static void test_main(void)
{
	struct bt_le_per_adv_sync *sync;
	struct bt_iso_big *big;

	init();
	scan_and_sync_pa(&sync);
	WAIT_FOR_FLAG(flag_biginfo);
	sync_big(sync, MIN(broadcaster_num_bis, CONFIG_BT_ISO_MAX_CHAN), &big);

	LOG_INF("Waiting for data");
	WAIT_FOR_FLAG(flag_data_received);
	bk_sync_send();

	LOG_INF("Waiting for sync lost");
	WAIT_FOR_FLAG_UNSET(flag_iso_connected);

	TEST_PASS("Test passed");
}

static const struct bst_test_instance test_def[] = {
	{
		.test_id = "receiver",
		.test_descr = "receiver",
		.test_post_init_f = test_init,
		.test_tick_f = test_tick,
		.test_main_f = test_main,
	},
	BSTEST_END_MARKER,
};

struct bst_test_list *test_main_bis_receiver_install(struct bst_test_list *tests)
{
	return bst_add_tests(tests, test_def);
}
Loading