From 927168fb5aba67a5012b4e36930972ef511a7aa0 Mon Sep 17 00:00:00 2001 From: Magdalena Kasenberg Date: Tue, 4 Jul 2023 13:51:00 +0200 Subject: [PATCH] bluetooth: tester: bap: Add support for AC 7(i) tests Allow to assing end points to CISes before creating a CIG. Previously the end points were assinged top-down, so the configuration 7(i) and other similar were not covered. Signed-off-by: Magdalena Kasenberg --- tests/bluetooth/tester/src/btp/btp_ascs.h | 17 ++ tests/bluetooth/tester/src/btp_bap.c | 225 ++++++++++++++++++---- 2 files changed, 208 insertions(+), 34 deletions(-) diff --git a/tests/bluetooth/tester/src/btp/btp_ascs.h b/tests/bluetooth/tester/src/btp/btp_ascs.h index 1cd279914e4..a5968a919af 100644 --- a/tests/bluetooth/tester/src/btp/btp_ascs.h +++ b/tests/bluetooth/tester/src/btp/btp_ascs.h @@ -73,6 +73,14 @@ struct btp_ascs_update_metadata_cmd { uint8_t ase_id; } __packed; +#define BTP_ASCS_ADD_ASE_TO_CIS 0x0a +struct btp_ascs_add_ase_to_cis { + bt_addr_le_t address; + uint8_t ase_id; + uint8_t cig_id; + uint8_t cis_id; +} __packed; + /* ASCS events */ #define BTP_ASCS_EV_OPERATION_COMPLETED 0x80 struct btp_ascs_operation_completed_ev { @@ -85,5 +93,14 @@ struct btp_ascs_operation_completed_ev { uint8_t flags; } __packed; +#define BTP_ASCS_EV_CHARACTERISTIC_SUBSCRIBED 0x81 + +#define BTP_ASCS_EV_ASE_STATE_CHANGED 0x82 +struct btp_ascs_ase_state_changed_ev { + bt_addr_le_t address; + uint8_t ase_id; + uint8_t state; +} __packed; + #define BTP_ASCS_STATUS_SUCCESS 0x00 #define BTP_ASCS_STATUS_FAILED 0x01 diff --git a/tests/bluetooth/tester/src/btp_bap.c b/tests/bluetooth/tester/src/btp_bap.c index 8591c43de04..27b3c9eb70e 100644 --- a/tests/bluetooth/tester/src/btp_bap.c +++ b/tests/bluetooth/tester/src/btp_bap.c @@ -56,6 +56,10 @@ struct audio_stream { size_t len_to_send; struct k_work_delayable audio_clock_work; struct k_work_delayable audio_send_work; + uint8_t cig_id; + uint8_t cis_id; + struct bt_bap_unicast_group **cig; + bool already_sent; }; #define MAX_STREAMS_COUNT MAX(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT, \ @@ -69,11 +73,12 @@ struct audio_connection { size_t configured_source_stream_count; struct bt_audio_codec_cfg codec_cfg; struct bt_audio_codec_qos qos; - struct bt_bap_unicast_group *unicast_group; struct bt_bap_ep *end_points[MAX_END_POINTS_COUNT]; size_t end_points_count; } connections[CONFIG_BT_MAX_CONN]; +static struct bt_bap_unicast_group *cigs[CONFIG_BT_ISO_MAX_CIG]; + static struct bt_audio_codec_qos_pref qos_pref = BT_AUDIO_CODEC_QOS_PREF(true, BT_GAP_LE_PHY_2M, 0x02, 10, 10000, 40000, 10000, 40000); @@ -84,8 +89,6 @@ NET_BUF_POOL_FIXED_DEFINE(tx_pool, MAX(CONFIG_BT_ASCS_ASE_SRC_COUNT, static struct net_buf_simple *rx_ev_buf = NET_BUF_SIMPLE(CONFIG_BT_ISO_RX_MTU + sizeof(struct btp_bap_stream_received_ev)); -static bool already_sent; - RING_BUF_DECLARE(audio_ring_buf, CONFIG_BT_ISO_TX_MTU); static void audio_clock_timeout(struct k_work *work); static void audio_send_timeout(struct k_work *work); @@ -222,6 +225,19 @@ static void btp_send_ascs_operation_completed_ev(struct bt_conn *conn, uint8_t a tester_event(BTP_SERVICE_ID_ASCS, BTP_ASCS_EV_OPERATION_COMPLETED, &ev, sizeof(ev)); } +static void btp_send_ascs_ase_state_changed_ev(struct bt_conn *conn, uint8_t ase_id, uint8_t state) +{ + struct btp_ascs_ase_state_changed_ev ev; + struct bt_conn_info info; + + (void)bt_conn_get_info(conn, &info); + bt_addr_le_copy(&ev.address, info.le.dst); + ev.ase_id = ase_id; + ev.state = state; + + tester_event(BTP_SERVICE_ID_ASCS, BTP_ASCS_EV_ASE_STATE_CHANGED, &ev, sizeof(ev)); +} + static int validate_codec_parameters(const struct bt_audio_codec_cfg *codec_cfg) { int freq_hz; @@ -528,6 +544,13 @@ static void stream_disabled(struct bt_bap_stream *stream) struct audio_stream *a_stream = CONTAINER_OF(stream, struct audio_stream, stream); LOG_DBG("Disabled stream %p", stream); + + if (stream->ep->dir == BT_AUDIO_DIR_SINK) { + /* Stop send timer */ + k_work_cancel_delayable(&a_stream->audio_clock_work); + k_work_cancel_delayable(&a_stream->audio_send_work); + } + btp_send_ascs_operation_completed_ev(stream->conn, a_stream->ase_id, BT_ASCS_DISABLE_OP, BTP_ASCS_STATUS_SUCCESS); } @@ -541,10 +564,17 @@ static void stream_released(struct bt_bap_stream *stream) audio_conn = &connections[a_stream->conn_id]; - if (audio_conn->unicast_group != NULL) { + if (stream->ep->dir == BT_AUDIO_DIR_SINK) { + /* Stop send timer */ + k_work_cancel_delayable(&a_stream->audio_clock_work); + k_work_cancel_delayable(&a_stream->audio_send_work); + } + + if (cigs[stream->ep->cig_id] != NULL) { + /* The unicast group will be deleted only at release of the last stream */ LOG_DBG("Deleting unicast group"); - int err = bt_bap_unicast_group_delete(audio_conn->unicast_group); + int err = bt_bap_unicast_group_delete(cigs[stream->ep->cig_id]); if (err != 0) { LOG_DBG("Unable to delete unicast group: %d", err); @@ -552,7 +582,7 @@ static void stream_released(struct bt_bap_stream *stream) return; } - audio_conn->unicast_group = NULL; + cigs[stream->ep->cig_id] = NULL; } a_stream->ase_id = 0; @@ -564,6 +594,8 @@ static void stream_started(struct bt_bap_stream *stream) struct bt_bap_ep_info info; int err; + /* Callback called on transition to Streaming state */ + LOG_DBG("Started stream %p", stream); err = bt_bap_ep_get_info(stream->ep, &info); @@ -587,8 +619,8 @@ static void stream_started(struct bt_bap_stream *stream) K_USEC(a_stream->stream.qos->interval)); } - btp_send_ascs_operation_completed_ev(stream->conn, a_stream->ase_id, - BT_ASCS_START_OP, BTP_STATUS_SUCCESS); + btp_send_ascs_ase_state_changed_ev(stream->conn, a_stream->ase_id, + stream->ep->status.state); } static void stream_stopped(struct bt_bap_stream *stream, uint8_t reason) @@ -597,7 +629,7 @@ static void stream_stopped(struct bt_bap_stream *stream, uint8_t reason) LOG_DBG("Stopped stream %p with reason 0x%02X", stream, reason); - if (stream->dir == BT_AUDIO_DIR_SINK) { + if (stream->ep->dir == BT_AUDIO_DIR_SINK) { /* Stop send timer */ k_work_cancel_delayable(&a_stream->audio_clock_work); k_work_cancel_delayable(&a_stream->audio_send_work); @@ -611,12 +643,14 @@ static void stream_recv(struct bt_bap_stream *stream, const struct bt_iso_recv_info *info, struct net_buf *buf) { - if (already_sent == false) { + struct audio_stream *a_stream = CONTAINER_OF(stream, struct audio_stream, stream); + + if (a_stream->already_sent == false) { /* For now, send just a first packet, to limit the number * of logs and not unnecessarily spam through btp. */ LOG_DBG("Incoming audio on stream %p len %u", stream, buf->len); - already_sent = true; + a_stream->already_sent = true; btp_send_stream_received_ev(stream->conn, stream->ep, buf->len, buf->data); } } @@ -749,8 +783,15 @@ static void enable_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rs static void start_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, enum bt_bap_ascs_reason reason) { + struct audio_stream *a_stream = CONTAINER_OF(stream, struct audio_stream, stream); + + /* Callback called on Receiver Start Ready notification from ASE Control Point */ + LOG_DBG("stream %p start operation rsp_code %u reason %u", stream, rsp_code, reason); - already_sent = false; + a_stream->already_sent = false; + + btp_send_ascs_operation_completed_ev(stream->conn, a_stream->ase_id, + BT_ASCS_START_OP, BTP_STATUS_SUCCESS); } static void stop_cb(struct bt_bap_stream *stream, enum bt_bap_ascs_rsp_code rsp_code, @@ -972,7 +1013,7 @@ static void audio_send_timeout(struct k_work *work) BT_ISO_TIMESTAMP_NONE); if (err != 0) { LOG_ERR("Failed to send audio data to stream: ase_id %d dir seq %d %d err %d", - stream->ase_id, stream->stream.dir, stream->last_req_seq_num, err); + stream->ase_id, stream->stream.ep->dir, stream->last_req_seq_num, err); net_buf_unref(buf); } @@ -1009,7 +1050,7 @@ static uint8_t bap_send(const void *cmd, uint16_t cmd_len, (void)bt_conn_get_info(conn, &conn_info); stream = stream_find(audio_conn, cp->ase_id); - if (stream == NULL || stream->stream.dir != BT_AUDIO_DIR_SINK) { + if (stream == NULL || stream->stream.ep->dir != BT_AUDIO_DIR_SINK) { return BTP_STATUS_FAILED; } @@ -1103,7 +1144,31 @@ static int server_stream_config(struct bt_conn *conn, struct bt_bap_stream *stre return 0; } -static int client_create_unicast_group(struct audio_connection *audio_conn, uint8_t ase_id) +static uint8_t client_add_ase_to_cis(struct audio_connection *audio_conn, uint8_t ase_id, + uint8_t cis_id, uint8_t cig_id) +{ + struct audio_stream *stream; + + if (cig_id >= CONFIG_BT_ISO_MAX_CIG || cis_id >= UNICAST_GROUP_STREAM_CNT) { + return BTP_STATUS_FAILED; + } + + stream = stream_find(audio_conn, ase_id); + if (stream == NULL) { + return BTP_STATUS_FAILED; + } + + LOG_DBG("Added ASE %u to CIS %u at CIG %u", ase_id, cis_id, cig_id); + + stream->cig = &cigs[cig_id]; + stream->cig_id = cig_id; + stream->cis_id = cis_id; + + return 0; +} + +static int client_create_unicast_group(struct audio_connection *audio_conn, uint8_t ase_id, + uint8_t cig_id) { int err; struct bt_bap_unicast_group_stream_pair_param pair_params[MAX_STREAMS_COUNT]; @@ -1112,13 +1177,31 @@ static int client_create_unicast_group(struct audio_connection *audio_conn, uint size_t stream_cnt = 0; size_t src_cnt = 0; size_t sink_cnt = 0; + size_t cis_cnt = 0; + (void)memset(pair_params, 0, sizeof(pair_params)); (void)memset(stream_params, 0, sizeof(stream_params)); - for (size_t i = 0; i < MAX_STREAMS_COUNT; i++) { - struct bt_bap_stream *stream = &audio_conn->streams[i].stream; + if (cig_id >= CONFIG_BT_ISO_MAX_CIG) { + return -EINVAL; + } - if (stream == NULL || stream->ep == NULL) { + /* API does not allow to assign a CIG ID freely, so ensure we create groups + * in the right order. + */ + for (uint8_t i = 0; i < cig_id; i++) { + if (cigs[cig_id] == NULL) { + return -EINVAL; + } + } + + /* Assign end points to CISes */ + for (size_t i = 0; i < MAX_STREAMS_COUNT; i++) { + struct audio_stream *a_stream = &audio_conn->streams[i]; + struct bt_bap_stream *stream = &a_stream->stream; + + if (stream == NULL || stream->ep == NULL || a_stream->cig == NULL || + a_stream->cig_id != cig_id) { continue; } @@ -1126,26 +1209,45 @@ static int client_create_unicast_group(struct audio_connection *audio_conn, uint stream_params[stream_cnt].qos = &audio_conn->qos; if (stream->ep->dir == BT_AUDIO_DIR_SOURCE) { - pair_params[src_cnt].rx_param = &stream_params[stream_cnt]; + if (pair_params[a_stream->cis_id].rx_param != NULL) { + return -EINVAL; + } + + pair_params[a_stream->cis_id].rx_param = &stream_params[stream_cnt]; src_cnt++; } else { - pair_params[sink_cnt].tx_param = &stream_params[stream_cnt]; + if (pair_params[a_stream->cis_id].tx_param != NULL) { + return -EINVAL; + } + + pair_params[a_stream->cis_id].tx_param = &stream_params[stream_cnt]; sink_cnt++; } stream_cnt++; } - if (stream_cnt == 0) { + /* Count CISes to be established */ + for (size_t i = 0; i < MAX_STREAMS_COUNT; i++) { + if (pair_params[i].tx_param == NULL && pair_params[i].rx_param == NULL) { + /* No gaps allowed */ + break; + } + + cis_cnt++; + } + + /* Make sure there are no gaps in the pair_params */ + if (cis_cnt == 0 || cis_cnt < MAX(sink_cnt, src_cnt)) { return -EINVAL; } param.params = pair_params; - param.params_count = MAX(sink_cnt, src_cnt); + param.params_count = cis_cnt; param.packing = BT_ISO_PACKING_SEQUENTIAL; LOG_DBG("Creating unicast group"); - err = bt_bap_unicast_group_create(¶m, &audio_conn->unicast_group); + err = bt_bap_unicast_group_create(¶m, &cigs[cig_id]); if (err != 0) { LOG_DBG("Could not create unicast group (err %d)", err); return -EINVAL; @@ -1305,15 +1407,20 @@ static uint8_t ascs_configure_qos(const void *cmd, uint16_t cmd_len, audio_conn = &connections[bt_conn_index(conn)]; - if (audio_conn->unicast_group != NULL) { - err = bt_bap_unicast_group_delete(audio_conn->unicast_group); + if (cigs[cp->cig_id] != NULL) { + err = bt_bap_unicast_group_delete(cigs[cp->cig_id]); if (err != 0) { LOG_DBG("Failed to delete the unicast group, err %d", err); bt_conn_unref(conn); return BTP_STATUS_FAILED; } - audio_conn->unicast_group = NULL; + cigs[cp->cig_id] = NULL; + } + + err = client_add_ase_to_cis(audio_conn, cp->ase_id, cp->cis_id, cp->cig_id); + if (err != 0) { + return BTP_STATUS_FAILED; } qos = &audio_conn->qos; @@ -1325,7 +1432,7 @@ static uint8_t ascs_configure_qos(const void *cmd, uint16_t cmd_len, qos->interval = sys_get_le24(cp->sdu_interval); qos->pd = sys_get_le24(cp->presentation_delay); - err = client_create_unicast_group(audio_conn, cp->ase_id); + err = client_create_unicast_group(audio_conn, cp->ase_id, cp->cig_id); if (err != 0) { LOG_DBG("Unable to create unicast group, err %d", err); bt_conn_unref(conn); @@ -1334,7 +1441,7 @@ static uint8_t ascs_configure_qos(const void *cmd, uint16_t cmd_len, } LOG_DBG("QoS configuring streams"); - err = bt_bap_stream_qos(conn, audio_conn->unicast_group); + err = bt_bap_stream_qos(conn, cigs[cp->cig_id]); bt_conn_unref(conn); if (err != 0) { @@ -1438,11 +1545,22 @@ static uint8_t ascs_receiver_start_ready(const void *cmd, uint16_t cmd_len, LOG_DBG("Starting stream %p, ep %u, dir %u", &stream->stream, cp->ase_id, stream->stream.ep->dir); - err = bt_bap_stream_start(&stream->stream); - if (err != 0) { - LOG_DBG("Could not start stream: %d", err); - return BTP_STATUS_FAILED; - } + while (true) { + err = bt_bap_stream_start(&stream->stream); + if (err == -EBUSY) { + /* TODO: How to determine if a controller is ready again after + * bt_bap_stream_start? In AC 6(i) tests the PTS sends Receiver Start Ready + * only after all CISes are established. + */ + k_sleep(K_MSEC(1000)); + continue; + } else if (err != 0) { + LOG_DBG("Could not start stream: %d", err); + return BTP_STATUS_FAILED; + } + + break; + }; return BTP_STATUS_SUCCESS; } @@ -1473,7 +1591,7 @@ static uint8_t ascs_receiver_stop_ready(const void *cmd, uint16_t cmd_len, LOG_DBG("Stopping stream"); err = bt_bap_stream_stop(&stream->stream); if (err != 0) { - LOG_DBG("Could not start stream: %d", err); + LOG_DBG("Could not stop stream: %d", err); return BTP_STATUS_FAILED; } @@ -1552,6 +1670,40 @@ static uint8_t ascs_update_metadata(const void *cmd, uint16_t cmd_len, void *rsp return BTP_STATUS_SUCCESS; } +static uint8_t ascs_add_ase_to_cis(const void *cmd, uint16_t cmd_len, void *rsp, uint16_t *rsp_len) +{ + int err; + struct bt_conn *conn; + struct audio_connection *audio_conn; + struct bt_conn_info conn_info; + const struct btp_ascs_add_ase_to_cis *cp = cmd; + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address); + if (!conn) { + LOG_ERR("Unknown connection"); + + return BTP_STATUS_FAILED; + } + + (void)bt_conn_get_info(conn, &conn_info); + + if (conn_info.role == BT_HCI_ROLE_PERIPHERAL) { + bt_conn_unref(conn); + + return BTP_STATUS_FAILED; + } + + audio_conn = &connections[bt_conn_index(conn)]; + bt_conn_unref(conn); + + err = client_add_ase_to_cis(audio_conn, cp->ase_id, cp->cis_id, cp->cig_id); + if (err != 0) { + return BTP_STATUS_FAILED; + } + + return BTP_STATUS_SUCCESS; +} + static const struct btp_handler ascs_handlers[] = { { .opcode = BTP_ASCS_READ_SUPPORTED_COMMANDS, @@ -1599,6 +1751,11 @@ static const struct btp_handler ascs_handlers[] = { .expect_len = sizeof(struct btp_ascs_update_metadata_cmd), .func = ascs_update_metadata, }, + { + .opcode = BTP_ASCS_ADD_ASE_TO_CIS, + .expect_len = sizeof(struct btp_ascs_add_ase_to_cis), + .func = ascs_add_ase_to_cis, + }, }; static int set_location(void)