From 79b774f22578593d11c72d64180331273b0abe07 Mon Sep 17 00:00:00 2001 From: Emil Gydesen Date: Thu, 9 Mar 2023 15:46:04 +0100 Subject: [PATCH] Bluetooth: GMAP: Add initial implementation of GMAP Add initial implementation of GMAP. Signed-off-by: Emil Gydesen --- include/zephyr/bluetooth/audio/gmap.h | 123 ++ .../zephyr/bluetooth/audio/gmap_lc3_preset.h | 162 +++ include/zephyr/bluetooth/uuid.h | 55 + subsys/bluetooth/audio/CMakeLists.txt | 2 + subsys/bluetooth/audio/Kconfig | 1 + subsys/bluetooth/audio/Kconfig.gmap | 161 +++ subsys/bluetooth/audio/gmap_client.c | 673 +++++++++ subsys/bluetooth/audio/gmap_server.c | 138 ++ subsys/bluetooth/audio/shell/CMakeLists.txt | 1 + subsys/bluetooth/audio/shell/audio.h | 3 + subsys/bluetooth/audio/shell/bap.c | 55 +- subsys/bluetooth/audio/shell/gmap.c | 940 +++++++++++++ subsys/bluetooth/shell/bt.c | 13 +- tests/bluetooth/shell/audio.conf | 28 +- tests/bsim/bluetooth/audio/prj.conf | 47 +- tests/bsim/bluetooth/audio/src/bap_common.h | 62 + .../audio/src/bap_unicast_client_test.c | 2 +- .../bluetooth/audio/src/bap_unicast_common.c | 2 +- .../bluetooth/audio/src/bap_unicast_common.h | 19 - .../audio/src/bap_unicast_server_test.c | 45 +- .../bluetooth/audio/src/cap_acceptor_test.c | 45 +- .../audio/src/cap_initiator_unicast_test.c | 2 +- tests/bsim/bluetooth/audio/src/common.c | 2 + tests/bsim/bluetooth/audio/src/common.h | 1 + .../bsim/bluetooth/audio/src/gmap_ugg_test.c | 1223 +++++++++++++++++ .../bsim/bluetooth/audio/src/gmap_ugt_test.c | 391 ++++++ tests/bsim/bluetooth/audio/src/main.c | 6 +- .../bluetooth/audio/test_scripts/_gmap.sh | 22 + .../audio/test_scripts/gmap_unicast_ac_1.sh | 39 + .../test_scripts/gmap_unicast_ac_11_i.sh | 43 + .../test_scripts/gmap_unicast_ac_11_ii.sh | 46 + .../audio/test_scripts/gmap_unicast_ac_2.sh | 38 + .../audio/test_scripts/gmap_unicast_ac_3.sh | 44 + .../audio/test_scripts/gmap_unicast_ac_4.sh | 37 + .../audio/test_scripts/gmap_unicast_ac_5.sh | 40 + .../audio/test_scripts/gmap_unicast_ac_6_i.sh | 37 + .../test_scripts/gmap_unicast_ac_6_ii.sh | 42 + .../test_scripts/gmap_unicast_ac_7_ii.sh | 46 + .../audio/test_scripts/gmap_unicast_ac_8_i.sh | 43 + .../test_scripts/gmap_unicast_ac_8_ii.sh | 46 + tests/bsim/sh_common.source | 1 - 41 files changed, 4589 insertions(+), 137 deletions(-) create mode 100644 include/zephyr/bluetooth/audio/gmap.h create mode 100644 include/zephyr/bluetooth/audio/gmap_lc3_preset.h create mode 100644 subsys/bluetooth/audio/Kconfig.gmap create mode 100644 subsys/bluetooth/audio/gmap_client.c create mode 100644 subsys/bluetooth/audio/gmap_server.c create mode 100644 subsys/bluetooth/audio/shell/gmap.c create mode 100644 tests/bsim/bluetooth/audio/src/bap_common.h delete mode 100644 tests/bsim/bluetooth/audio/src/bap_unicast_common.h create mode 100644 tests/bsim/bluetooth/audio/src/gmap_ugg_test.c create mode 100644 tests/bsim/bluetooth/audio/src/gmap_ugt_test.c create mode 100755 tests/bsim/bluetooth/audio/test_scripts/_gmap.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_1.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_11_i.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_11_ii.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_2.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_3.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_4.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_5.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_6_i.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_6_ii.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_7_ii.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_8_i.sh create mode 100755 tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_8_ii.sh diff --git a/include/zephyr/bluetooth/audio/gmap.h b/include/zephyr/bluetooth/audio/gmap.h new file mode 100644 index 000000000000000..5524dabffec68be --- /dev/null +++ b/include/zephyr/bluetooth/audio/gmap.h @@ -0,0 +1,123 @@ +/** @file + * @brief Header for Bluetooth GMAP. + * + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_GMAP_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_GMAP_ + +#include +#include + +/** + * @brief Bluetooth Gaming Profile (GMAP) + * @defgroup bt_gmap Bluetooth Gaming Profile + * @ingroup bluetooth + * @{ + */ + +/** Gaming Role bitfield */ +enum bt_gmap_role { + /** Gaming Role Unicast Game Gateway */ + BT_GMAP_ROLE_UGG = BIT(0), + /** Gaming Role Unicast Game Terminal */ + BT_GMAP_ROLE_UGT = BIT(1), + /** Gaming Role Broadcast Game Sender */ + BT_GMAP_ROLE_BGS = BIT(2), + /** Gaming Role Broadcast Game Receiver */ + BT_GMAP_ROLE_BGR = BIT(3), +}; + +/* TBD: Should we declare a single enum for feature supported, and then transform from the spec + * defined values to Zephyr define values + */ + +/** Unicast Game Gateway Feature bitfield */ +enum bt_gmap_ugg_feat { + /** Multiplex support */ + BT_GMAP_UGG_FEAT_MULTIPLEX = BIT(0), + /** 96 kbps support */ + BT_GMAP_UGG_FEAT_96KBPS = BIT(1), + /** Multisink support */ + BT_GMAP_UGG_FEAT_MULTISINK = BIT(2), +}; + +/** Unicast Game Terminal Feature bitfield */ +enum bt_gmap_ugt_feat { + /** Source support */ + BT_GMAP_UGT_FEAT_SOURCE = BIT(0), + /** 80 kbps source support */ + BT_GMAP_UGT_FEAT_80KBPS_SOURCE = BIT(1), + /** Sink support */ + BT_GMAP_UGT_FEAT_SINK = BIT(2), + /** 64 kbps sink support */ + BT_GMAP_UGT_FEAT_64KBPS_SINK = BIT(3), + /** Multiplex support */ + BT_GMAP_UGT_FEAT_MULTIPLEX = BIT(4), + /** Multisink support */ + BT_GMAP_UGT_FEAT_MULTISINK = BIT(5), + /** Multisource support */ + BT_GMAP_UGT_FEAT_MULTISOURCE = BIT(6), +}; + +/** Broadcast Game Sender Feature bitfield */ +enum bt_gmap_bgs_feat { + /** 96 kbps support */ + BT_GMAP_BGS_FEAT_96KBPS = BIT(0), +}; + +/** Broadcast Game Receiver Feature bitfield */ +enum bt_gmap_bgr_feat { + /** Multisink support */ + BT_GMAP_BGR_FEAT_MULTISINK = BIT(0), + /** Multiplex support */ + BT_GMAP_BGR_FEAT_MULTIPLEX = BIT(1), +}; + +/** @brief Hearing Access Service Client callback structure. */ +struct bt_gmap_cb { + /** + * @brief Callback function for bt_has_discover. + * + * This callback is called when discovery procedure is complete. + * + * @param conn Bluetooth connection object. + * @param err 0 on success, ATT error or negative errno otherwise. + * @param role Role of remote device. 0 on failure. + * @param ugg_feat Remote Unicast Game Gateway features. 0 if not supported or on error. + * @param ugt_feat Remote Unicast Game Terminal features. 0 if not supported or on error. + * @param bgs_feat Remote Broadcast Game Sender features. 0 if not supported or on error. + * @param bgr_feat Remote Broadcast Game Receiver features. 0 if not supported or on error. + */ + void (*discover)(struct bt_conn *conn, int err, enum bt_gmap_role role, + enum bt_gmap_ugg_feat ugg_feat, enum bt_gmap_ugt_feat ugt_feat, + enum bt_gmap_bgs_feat bgs_feat, enum bt_gmap_bgr_feat bgr_feat); +}; + +/** @brief Registers the callbacks used by the Gaming Profile. + * + * @param cb The callback structure. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_gmap_cb_register(const struct bt_gmap_cb *cb); + +/** + * @brief Discover Gaming Service on a remote device. + * + * Procedure to find a Gaming Service on a server identified by @p conn. + * The @ref bt_gmap_cb.discover callback is called when the discovery procedure completes of fails. + * On discovery success the callback contains information about the remote device. + * + * @param conn Bluetooth connection object. + * + * @return 0 if success, errno on failure. + */ +int bt_gmap_discover(struct bt_conn *conn); + +/** @} */ /* end of bt_gmap */ + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_GMAP_ */ diff --git a/include/zephyr/bluetooth/audio/gmap_lc3_preset.h b/include/zephyr/bluetooth/audio/gmap_lc3_preset.h new file mode 100644 index 000000000000000..20da1d622cfdad2 --- /dev/null +++ b/include/zephyr/bluetooth/audio/gmap_lc3_preset.h @@ -0,0 +1,162 @@ +/** @file + * @brief Header for Bluetooth GMAP LC3 presets. + * + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_GMAP_LC3_PRESET_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_GMAP_LC3_PRESET_ + +#include + +/* GMAP LC3 unicast presets defined by table 3.16 in the GMAP v1.0 specification */ + +/** + * @brief Helper to declare LC3 32_1_gr codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_32_1_GR(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_32_1(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_7_5_UNFRAMED(60U, 1U, 15U, 10000U)) + +/** + * @brief Helper to declare LC3 32_2_gr codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_32_2_GR(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_32_2(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_10_UNFRAMED(80U, 1U, 20U, 10000U)) + +/** + * @brief Helper to declare LC3 48_1_gr codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_1_GR(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_1(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_7_5_UNFRAMED(75U, 1U, 15U, 10000U)) + +/** + * @brief Helper to declare LC3 48_2_gr codec configuration + * + * Mandatory to support as both unicast client and server + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_2_GR(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_2(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_10_UNFRAMED(100U, 1U, 20U, 10000U)) + +/** + * @brief Helper to declare LC3 48_3_gr codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_3_GR(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_3(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_7_5_UNFRAMED(90U, 1U, 15U, 10000U)) + +/** + * @brief Helper to declare LC3 48_4_gr codec configuration + * + * Mandatory to support as unicast server + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_4_GR(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_4(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_10_UNFRAMED(120U, 1U, 20U, 10000U)) + +/** + * @brief Helper to declare LC3 32_1_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_32_1_GS(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_32_1(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_7_5_UNFRAMED(60U, 1U, 15U, 21500U)) + +/** + * @brief Helper to declare LC3 32_2_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_32_2_GS(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_32_2(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_10_UNFRAMED(80U, 1U, 20U, 22500U)) + +/** + * @brief Helper to declare LC3 48_1_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_1_GS(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_1(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_7_5_UNFRAMED(75U, 1U, 15U, 21500U)) + +/** + * @brief Helper to declare LC3 48_2_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_2_GS(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_2(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_10_UNFRAMED(100U, 1U, 20U, 22500U)) + +/* GMAP LC3 broadcast presets defined by table 3.22 in the GMAP v1.0 specification */ + +/** + * @brief Helper to declare LC3 48_1_g codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_1_G(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_1(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_7_5_UNFRAMED(75U, 1U, 8U, 10000U)) + +/** + * @brief Helper to declare LC3 48_2_g codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_2_G(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_2(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_10_UNFRAMED(100U, 1U, 10U, 10000U)) + +/** + * @brief Helper to declare LC3 48_3_g codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_3_G(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_3(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_7_5_UNFRAMED(90U, 1U, 8U, 10000U)) + +/** + * @brief Helper to declare LC3 48_4_g codec configuration + * + * @param _loc Audio channel location bitfield (@ref bt_audio_location) + * @param _stream_context Stream context (BT_AUDIO_CONTEXT_*) + */ +#define BT_GMAP_LC3_PRESET_48_4_G(_loc, _stream_context) \ + BT_BAP_LC3_PRESET(BT_CODEC_LC3_CONFIG_48_4(_loc, _stream_context), \ + BT_CODEC_LC3_QOS_10_UNFRAMED(120U, 1U, 10U, 10000U)) + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_GMAP_LC3_PRESET_ */ diff --git a/include/zephyr/bluetooth/uuid.h b/include/zephyr/bluetooth/uuid.h index 1fa8ece64669ab2..2c0948b8782c780 100644 --- a/include/zephyr/bluetooth/uuid.h +++ b/include/zephyr/bluetooth/uuid.h @@ -5090,6 +5090,61 @@ struct bt_uuid_128 { */ #define BT_UUID_GATT_SL \ BT_UUID_DECLARE_16(BT_UUID_GATT_SL_VAL) + +/** + * @brief Gaming Service UUID value + */ +#define BT_UUID_GMAS_VAL 0x1858 +/** + * @brief Common Audio Service + */ +#define BT_UUID_GMAS BT_UUID_DECLARE_16(BT_UUID_GMAS_VAL) + +/** + * @brief Gaming Profile Role UUID value + */ +#define BT_UUID_GMAP_ROLE_VAL 0x2C00 +/** + * @brief Gaming Profile Role + */ +#define BT_UUID_GMAP_ROLE BT_UUID_DECLARE_16(BT_UUID_GMAP_ROLE_VAL) + +/** + * @brief Gaming Profile Unicast Game Gateway Features UUID value + */ +#define BT_UUID_GMAP_UGG_FEAT_VAL 0x2C01 +/** + * @brief Gaming Profile Unicast Game Gateway Features + */ +#define BT_UUID_GMAP_UGG_FEAT BT_UUID_DECLARE_16(BT_UUID_GMAP_UGG_FEAT_VAL) + +/** + * @brief Gaming Profile Unicast Game Terminal Features UUID value + */ +#define BT_UUID_GMAP_UGT_FEAT_VAL 0x2C02 +/** + * @brief Gaming Profile Unicast Game Terminal Features + */ +#define BT_UUID_GMAP_UGT_FEAT BT_UUID_DECLARE_16(BT_UUID_GMAP_UGT_FEAT_VAL) + +/** + * @brief Gaming Profile Broadcast Game Sender Features UUID value + */ +#define BT_UUID_GMAP_BGS_FEAT_VAL 0x2C03 +/** + * @brief Gaming Profile Broadcast Game Sender Features + */ +#define BT_UUID_GMAP_BGS_FEAT BT_UUID_DECLARE_16(BT_UUID_GMAP_BGS_FEAT_VAL) + +/** + * @brief Gaming Profile Broadcast Game Receiver Features UUID value + */ +#define BT_UUID_GMAP_BGR_FEAT_VAL 0x2C04 +/** + * @brief Gaming Profile Broadcast Game Receiver Features + */ +#define BT_UUID_GMAP_BGR_FEAT BT_UUID_DECLARE_16(BT_UUID_GMAP_BGR_FEAT_VAL) + /* * Protocol UUIDs */ diff --git a/subsys/bluetooth/audio/CMakeLists.txt b/subsys/bluetooth/audio/CMakeLists.txt index 8f24c50233b62b6..c8059170c403287 100644 --- a/subsys/bluetooth/audio/CMakeLists.txt +++ b/subsys/bluetooth/audio/CMakeLists.txt @@ -61,3 +61,5 @@ zephyr_library_sources_ifdef(CONFIG_BT_CAP cap_stream.c) zephyr_library_sources_ifdef(CONFIG_BT_CAP_ACCEPTOR cap_acceptor.c) zephyr_library_sources_ifdef(CONFIG_BT_CAP_INITIATOR cap_initiator.c) zephyr_library_sources_ifdef(CONFIG_BT_TMAP tmap.c) +zephyr_library_sources_ifdef(CONFIG_BT_GMAS gmap_server.c) +zephyr_library_sources_ifdef(CONFIG_BT_GMAP gmap_client.c) diff --git a/subsys/bluetooth/audio/Kconfig b/subsys/bluetooth/audio/Kconfig index 2137151e9e72aba..ea9b58fbc24dc27 100644 --- a/subsys/bluetooth/audio/Kconfig +++ b/subsys/bluetooth/audio/Kconfig @@ -51,6 +51,7 @@ rsource "Kconfig.mpl" rsource "Kconfig.mctl" rsource "Kconfig.cap" rsource "Kconfig.tmap" +rsource "Kconfig.gmap" module = BT_AUDIO module-str = "Bluetooth Audio" diff --git a/subsys/bluetooth/audio/Kconfig.gmap b/subsys/bluetooth/audio/Kconfig.gmap new file mode 100644 index 000000000000000..67baa8555237b8f --- /dev/null +++ b/subsys/bluetooth/audio/Kconfig.gmap @@ -0,0 +1,161 @@ +# Bluetooth Audio - Gaming Profile (CAP) options +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +config BT_GMAP + def_bool BT_GMAP_UGG || BT_GMAP_UGT || BT_GMAP_BGS || BT_GMAP_BGR + +config BT_GMAS + def_bool BT_GMAP_UGG || BT_GMAP_UGT || BT_GMAP_BGS_SERVICE || BT_GMAP_BGR + +config BT_GMAP_UGG + bool "Gaming Profile Unicast Game Gateway Role Support [EXPERIMENTAL]" + depends on BT_CAP_INITIATOR && BT_BAP_UNICAST_CLIENT && BT_VCP_VOL_CTLR + # TODO: Add BT_CAP_COMMANDER dependency + select EXPERIMENTAL + help + Enabling this will enable the GMAP Unicast Game Gateway role. + This instantiates the Gaming Service (GMAS). + +config BT_GMAP_UGG_MULTIPLEX_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Multiplex Support [EXPERIMENTAL]" + depends on BT_GMAP_UGG + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGG multiplex feature + is supported. + +config BT_GMAP_UGG_96KBPS_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role 96 kbps Support [EXPERIMENTAL]" + depends on BT_GMAP_UGG + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGG 96 kbps Source feature + is supported. + +config BT_GMAP_UGG_MULTISINK_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Multisink Support [EXPERIMENTAL]" + depends on BT_GMAP_UGG + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGG Multisink feature + is supported. + +config BT_GMAP_UGT + bool "Gaming Profile Unicast Game Gateway Role Support [EXPERIMENTAL]" + depends on BT_CAP_ACCEPTOR && BT_BAP_UNICAST_SERVER + select EXPERIMENTAL + help + Enabling this will enable the GMAP Unicast Game Terminal role. + This instantiates the Gaming Service (GMAS). + +config BT_GMAP_UGT_SOURCE_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Source Support [EXPERIMENTAL]" + depends on BT_GMAP_UGT && BT_MICP_MIC_DEV + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGT Source feature + is supported. + +config BT_GMAP_UGT_80KBPS_SOURCE_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role 80 kbps Source Support [EXPERIMENTAL]" + depends on BT_GMAP_UGT_SOURCE_SUPPORT + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGT 80 KBPS Source feature + is supported. + +config BT_GMAP_UGT_SINK_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Sink Support [EXPERIMENTAL]" + depends on BT_GMAP_UGT && BT_VCP_VOL_REND + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGT Sink feature + is supported. + +config BT_GMAP_UGT_64KBPS_SINK_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role 64 kbps Sink Support [EXPERIMENTAL]" + depends on BT_GMAP_UGT_SINK_SUPPORT + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGT 64 kbps Sink feature + is supported. + +config BT_GMAP_UGT_MULTIPLEX_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Multiplex Support [EXPERIMENTAL]" + depends on BT_GMAP_UGT + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGT Multiplex feature + is supported. + +config BT_GMAP_UGT_MULTISINK_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Multisink Support [EXPERIMENTAL]" + depends on BT_GMAP_UGT_SINK_SUPPORT + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGT Multisink feature + is supported. + +config BT_GMAP_UGT_MULTISOURCE_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Multisource Support [EXPERIMENTAL]" + depends on BT_GMAP_UGT_SOURCE_SUPPORT + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP UGT Multisource feature + is supported. + +config BT_GMAP_BGS + bool "Gaming Profile Broadcast Game Sender Role Support [EXPERIMENTAL]" + depends on BT_CAP_INITIATOR && BT_BAP_BROADCAST_SOURCE + # TODO: Add BT_CAP_COMMANDER dependency + select EXPERIMENTAL + help + Enabling this will enable the GMAP Broadcast Game Sender role. + +config BT_GMAP_BGS_SERVICE + bool "Gaming Profile Broadcast Game Sender Role Support [EXPERIMENTAL]" + depends on BT_GMAP_BGS + select EXPERIMENTAL + help + Enabling this will instantiate the Gaming Service (GMAS). + +config BT_GMAP_BGS_96KBPS_SUPPORT + bool "Gaming Profile Broadcast Game Sender Role 96 kbps Support [EXPERIMENTAL]" + depends on BT_GMAP_BGS + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP BGS Multisource feature + is supported. + +config BT_GMAP_BGR + bool "Gaming Profile Broadcast Game Receiver Role Support [EXPERIMENTAL]" + depends on BT_CAP_ACCEPTOR && BT_BAP_BROADCAST_SINK && BT_VCP_VOL_REND + select EXPERIMENTAL + help + Enabling this will enable the GMAP Broadcast Game Receiver Role role. + This instantiates the Gaming Service (GMAS). + +config BT_GMAP_BGR_MULTISINK_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Multisink Support [EXPERIMENTAL]" + depends on BT_GMAP_BGR + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP BGR Multisink feature + is supported. + +config BT_GMAP_BGR_MULTIPLEX_SUPPORT + bool "Gaming Profile Unicast Game Gateway Role Multiplex Support [EXPERIMENTAL]" + depends on BT_GMAP_BGR + select EXPERIMENTAL + help + Enabling this will let remote devices know that the GMAP BGR Multiplex feature + is supported. + +parent-module = BT +module = BT_GMAP +module-str = "Bluetooth Gaming Profile" +source "subsys/logging/Kconfig.template.log_config_inherit" diff --git a/subsys/bluetooth/audio/gmap_client.c b/subsys/bluetooth/audio/gmap_client.c new file mode 100644 index 000000000000000..a83280736978fe9 --- /dev/null +++ b/subsys/bluetooth/audio/gmap_client.c @@ -0,0 +1,673 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "audio_internal.h" + +LOG_MODULE_REGISTER(bt_gmap_client, CONFIG_BT_GMAP_LOG_LEVEL); + +static const struct bt_uuid *gmas_uuid = BT_UUID_GMAS; +static const struct bt_uuid *gmap_role_uuid = BT_UUID_GMAP_ROLE; +static const struct bt_uuid *gmap_ugg_feat_uuid = BT_UUID_GMAP_UGG_FEAT; +static const struct bt_uuid *gmap_ugt_feat_uuid = BT_UUID_GMAP_UGT_FEAT; +static const struct bt_uuid *gmap_bgs_feat_uuid = BT_UUID_GMAP_BGS_FEAT; +static const struct bt_uuid *gmap_bgr_feat_uuid = BT_UUID_GMAP_BGR_FEAT; + +static const struct bt_gmap_cb *gmap_cb; + +static struct bt_gmap_client { + /** Profile connection reference */ + struct bt_conn *conn; + + /* Remote role and features */ + enum bt_gmap_role role; + enum bt_gmap_ugg_feat ugg_feat; + enum bt_gmap_ugt_feat ugt_feat; + enum bt_gmap_bgs_feat bgs_feat; + enum bt_gmap_bgr_feat bgr_feat; + + uint16_t svc_start_handle; + uint16_t svc_end_handle; + + bool busy; + + /* GATT procedure parameters */ + union { + struct bt_gatt_read_params read; + struct bt_gatt_discover_params discover; + } params; +} gmap_insts[CONFIG_BT_MAX_CONN]; + +static void gmap_reset(struct bt_gmap_client *gmap_cli) +{ + if (gmap_cli->conn != NULL) { + bt_conn_unref(gmap_cli->conn); + } + + memset(gmap_cli, 0, sizeof(*gmap_cli)); +} + +static struct bt_gmap_client *client_by_conn(struct bt_conn *conn) +{ + struct bt_gmap_client *gmap_cli = &gmap_insts[bt_conn_index(conn)]; + + if (gmap_cli->conn == conn) { + return gmap_cli; + } + + return NULL; +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + + if (gmap_cli != NULL) { + bt_conn_unref(gmap_cli->conn); + gmap_cli->conn = NULL; + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = { + .disconnected = disconnected, +}; + +static void discover_complete(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + gmap_cli->busy = false; + + if (gmap_cb->discover != NULL) { + gmap_cb->discover(gmap_cli->conn, 0, gmap_cli->role, gmap_cli->ugg_feat, + gmap_cli->ugt_feat, gmap_cli->bgs_feat, gmap_cli->bgr_feat); + } +} + +static void discover_failed(struct bt_gmap_client *gmap_cli, int err) +{ + struct bt_conn *conn = gmap_cli->conn; + + gmap_reset(gmap_cli); + + LOG_DBG("conn %p err %d", (void *)conn, err); + + gmap_cb->discover(conn, err, 0, 0, 0, 0, 0); +} + +static uint8_t bgr_feat_read_cb(struct bt_conn *conn, uint8_t att_err, + struct bt_gatt_read_params *params, const void *data, uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + __ASSERT(gmap_cli, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, att_err, params, + data, len); + + if (data == NULL || att_err != BT_ATT_ERR_SUCCESS || len != sizeof(uint8_t)) { + if (att_err == BT_ATT_ERR_SUCCESS) { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->bgr_feat = net_buf_simple_pull_u8(&buf); + LOG_DBG("bgr_feat 0x%02x", gmap_cli->bgr_feat); + + discover_complete(gmap_cli); + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_bgr_feat(struct bt_gmap_client *gmap_cli, uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = bgr_feat_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t bgr_feat_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + __ASSERT(gmap_cli != NULL, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + err = gmap_read_bgr_feat(gmap_cli, chrc->value_handle); + if (err != 0) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_bgr_feat(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = bgr_feat_discover_func; + gmap_cli->params.discover.uuid = gmap_bgr_feat_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t bgs_feat_read_cb(struct bt_conn *conn, uint8_t att_err, + struct bt_gatt_read_params *params, const void *data, uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + __ASSERT(gmap_cli, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, att_err, params, + data, len); + + if (data == NULL || att_err != BT_ATT_ERR_SUCCESS || len != sizeof(uint8_t)) { + if (att_err == BT_ATT_ERR_SUCCESS) { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->bgs_feat = net_buf_simple_pull_u8(&buf); + LOG_DBG("bgs_feat 0x%02x", gmap_cli->bgs_feat); + + if ((gmap_cli->role & BT_GMAP_ROLE_BGR) != 0) { + err = gmap_discover_bgr_feat(gmap_cli); + } else { + discover_complete(gmap_cli); + return BT_GATT_ITER_STOP; + } + + if (err) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_bgs_feat(struct bt_gmap_client *gmap_cli, uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = bgs_feat_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t bgs_feat_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + __ASSERT(gmap_cli != NULL, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + err = gmap_read_bgs_feat(gmap_cli, chrc->value_handle); + if (err != 0) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_bgs_feat(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = bgs_feat_discover_func; + gmap_cli->params.discover.uuid = gmap_bgs_feat_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t ugt_feat_read_cb(struct bt_conn *conn, uint8_t att_err, + struct bt_gatt_read_params *params, const void *data, uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + __ASSERT(gmap_cli, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, att_err, params, + data, len); + + if (data == NULL || att_err != BT_ATT_ERR_SUCCESS || len != sizeof(uint8_t)) { + if (att_err == BT_ATT_ERR_SUCCESS) { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->ugt_feat = net_buf_simple_pull_u8(&buf); + LOG_DBG("ugt_feat 0x%02x", gmap_cli->ugt_feat); + + if ((gmap_cli->role & BT_GMAP_ROLE_BGS) != 0) { + err = gmap_discover_bgs_feat(gmap_cli); + } else if ((gmap_cli->role & BT_GMAP_ROLE_BGR) != 0) { + err = gmap_discover_bgr_feat(gmap_cli); + } else { + discover_complete(gmap_cli); + return BT_GATT_ITER_STOP; + } + + if (err) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_ugt_feat(struct bt_gmap_client *gmap_cli, uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = ugt_feat_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t ugt_feat_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + __ASSERT(gmap_cli != NULL, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + err = gmap_read_ugt_feat(gmap_cli, chrc->value_handle); + if (err != 0) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_ugt_feat(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = ugt_feat_discover_func; + gmap_cli->params.discover.uuid = gmap_ugt_feat_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t ugg_feat_read_cb(struct bt_conn *conn, uint8_t att_err, + struct bt_gatt_read_params *params, const void *data, uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + __ASSERT(gmap_cli, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, att_err, params, + data, len); + + if (data == NULL || att_err != BT_ATT_ERR_SUCCESS || len != sizeof(uint8_t)) { + if (att_err == BT_ATT_ERR_SUCCESS) { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->ugg_feat = net_buf_simple_pull_u8(&buf); + LOG_DBG("ugg_feat 0x%02x", gmap_cli->ugg_feat); + + if ((gmap_cli->role & BT_GMAP_ROLE_UGT) != 0) { + err = gmap_discover_ugt_feat(gmap_cli); + } else if ((gmap_cli->role & BT_GMAP_ROLE_BGS) != 0) { + err = gmap_discover_bgs_feat(gmap_cli); + } else if ((gmap_cli->role & BT_GMAP_ROLE_BGR) != 0) { + err = gmap_discover_bgr_feat(gmap_cli); + } else { + discover_complete(gmap_cli); + return BT_GATT_ITER_STOP; + } + + if (err) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_ugg_feat(struct bt_gmap_client *gmap_cli, uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = ugg_feat_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t ugg_feat_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + __ASSERT(gmap_cli != NULL, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + err = gmap_read_ugg_feat(gmap_cli, chrc->value_handle); + if (err != 0) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_ugg_feat(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = ugg_feat_discover_func; + gmap_cli->params.discover.uuid = gmap_ugg_feat_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t role_read_cb(struct bt_conn *conn, uint8_t att_err, + struct bt_gatt_read_params *params, const void *data, uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + __ASSERT(gmap_cli, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, att_err, params, + data, len); + + if (data == NULL || att_err != BT_ATT_ERR_SUCCESS || len != sizeof(uint8_t)) { + if (att_err == BT_ATT_ERR_SUCCESS) { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->role = net_buf_simple_pull_u8(&buf); + LOG_DBG("role 0x%02x", gmap_cli->role); + + if ((gmap_cli->role & BT_GMAP_ROLE_UGG) != 0) { + err = gmap_discover_ugg_feat(gmap_cli); + } else if ((gmap_cli->role & BT_GMAP_ROLE_UGT) != 0) { + err = gmap_discover_ugt_feat(gmap_cli); + } else if ((gmap_cli->role & BT_GMAP_ROLE_BGS) != 0) { + err = gmap_discover_bgs_feat(gmap_cli); + } else if ((gmap_cli->role & BT_GMAP_ROLE_BGR) != 0) { + err = gmap_discover_bgr_feat(gmap_cli); + } else { + LOG_DBG("Remote device does not support any known roles"); + err = -ECANCELED; + } + + if (err) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_role(struct bt_gmap_client *gmap_cli, uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = role_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t role_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + __ASSERT(gmap_cli != NULL, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + err = gmap_read_role(gmap_cli, chrc->value_handle); + if (err != 0) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_role(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = role_discover_func; + gmap_cli->params.discover.uuid = gmap_role_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t gmas_discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_service_val *svc; + int err; + + __ASSERT(gmap_cli != NULL, "no instance for conn %p", (void *)conn); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + svc = (struct bt_gatt_service_val *)attr->user_data; + gmap_cli->svc_start_handle = attr->handle; + gmap_cli->svc_end_handle = svc->end_handle; + + err = gmap_discover_role(gmap_cli); + if (err != 0) { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +int bt_gmap_discover(struct bt_conn *conn) +{ + struct bt_gmap_client *gmap_cli; + int err; + + CHECKIF(conn == NULL) { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + gmap_cli = &gmap_insts[bt_conn_index(conn)]; + + if (gmap_cli->busy) { + LOG_DBG("Busy"); + return -EBUSY; + } + + gmap_reset(gmap_cli); + + gmap_cli->params.discover.func = gmas_discover_func; + gmap_cli->params.discover.uuid = gmas_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_PRIMARY; + gmap_cli->params.discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + gmap_cli->params.discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + err = bt_gatt_discover(conn, &gmap_cli->params.discover); + if (err == 0) { + gmap_cli->conn = bt_conn_ref(conn); + } + + return err; +} + +int bt_gmap_cb_register(const struct bt_gmap_cb *cb) +{ + CHECKIF(cb == NULL) { + return -EINVAL; + } + + if (gmap_cb != NULL) { + return -EALREADY; + } + + gmap_cb = cb; + + return 0; +} diff --git a/subsys/bluetooth/audio/gmap_server.c b/subsys/bluetooth/audio/gmap_server.c new file mode 100644 index 000000000000000..5910192dec2818e --- /dev/null +++ b/subsys/bluetooth/audio/gmap_server.c @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +#include "audio_internal.h" + +LOG_MODULE_REGISTER(bt_gmap_server, CONFIG_BT_GMAP_LOG_LEVEL); + +static ssize_t read_gmap_role(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const uint8_t role = (IS_ENABLED(CONFIG_BT_GMAP_UGG) ? BT_GMAP_ROLE_UGG : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGT) ? BT_GMAP_ROLE_UGT : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_BGS) ? BT_GMAP_ROLE_BGS : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_BGR) ? BT_GMAP_ROLE_BGR : 0); + + LOG_DBG("role 0x%02X", role); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &role, sizeof(role)); +} + +#if defined(CONFIG_BT_GMAP_UGG) +static ssize_t read_gmap_ugg_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + /* TODO: We should be able to make this a const */ + const uint8_t feat = (IS_ENABLED(CONFIG_BT_GMAP_UGG_MULTIPLEX_SUPPORT) ? + BT_GMAP_UGG_FEAT_MULTIPLEX : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGG_96KBPS_SUPPORT) ? + BT_GMAP_UGG_FEAT_96KBPS : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGG_MULTISINK_SUPPORT) ? + BT_GMAP_UGG_FEAT_MULTISINK : 0); + + LOG_DBG("feat 0x%02X", feat); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat)); +} +#endif /* CONFIG_BT_GMAP_UGG */ + +#if defined(CONFIG_BT_GMAP_UGT) +BUILD_ASSERT(IS_ENABLED(CONFIG_BT_GMAP_UGT_SOURCE_SUPPORT) || + IS_ENABLED(CONFIG_BT_GMAP_UGT_SINK_SUPPORT), + "GMAP UGT shall support either source or sink"); + +#if defined(CONFIG_BT_GMAP_BGR_MULTIPLEX_SUPPORT) && defined(CONFIG_BT_GMAP_UGT_SINK_SUPPORT) +BUILD_ASSERT( + IS_ENABLED(CONFIG_BT_GMAP_UGT_MULTIPLEX_SUPPORT), + "CONFIG_BT_GMAP_UGT_MULTIPLEX_SUPPORT shall be supported if " + "CONFIG_BT_GMAP_UGT_SINK_SUPPORT and CONFIG_BT_GMAP_BGR_MULTIPLEX_SUPPORT are supported"); +#endif /* CONFIG_BT_GMAP_BGR_MULTIPLEX_SUPPORT && CONFIG_BT_GMAP_UGT_SINK_SUPPORT */ + +static ssize_t read_gmap_ugt_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const uint8_t feat = (IS_ENABLED(CONFIG_BT_GMAP_UGT_SOURCE_SUPPORT) ? + BT_GMAP_UGT_FEAT_SOURCE : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGT_80KBPS_SOURCE_SUPPORT) ? + BT_GMAP_UGT_FEAT_80KBPS_SOURCE : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGT_SINK_SUPPORT) ? + BT_GMAP_UGT_FEAT_SINK : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGT_64KBPS_SINK_SUPPORT) ? + BT_GMAP_UGT_FEAT_64KBPS_SINK : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGT_MULTIPLEX_SUPPORT) ? + BT_GMAP_UGT_FEAT_MULTIPLEX : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGT_MULTISINK_SUPPORT) ? + BT_GMAP_UGT_FEAT_MULTISINK : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGT_MULTISOURCE_SUPPORT) ? + BT_GMAP_UGT_FEAT_MULTISOURCE : 0); + + LOG_DBG("feat 0x%02X", feat); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat)); +} +#endif /* CONFIG_BT_GMAP_UGT */ + +#if defined(CONFIG_BT_GMAP_BGS) +static ssize_t read_gmap_bgs_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const uint8_t feat = IS_ENABLED(CONFIG_BT_GMAP_BGS_96KBPS_SUPPORT) ? + BT_GMAP_BGS_FEAT_96KBPS : 0; + + LOG_DBG("feat 0x%02X", feat); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat)); +} +#endif /* CONFIG_BT_GMAP_BGS */ + +#if defined(CONFIG_BT_GMAP_BGR) + +#if defined(CONFIG_BT_GMAP_UGT_MULTIPLEX_SUPPORT) +BUILD_ASSERT(IS_ENABLED(CONFIG_BT_GMAP_BGR_MULTIPLEX_SUPPORT), + "CONFIG_BT_GMAP_BGR_MULTIPLEX_SUPPORT shall be supported if " + "CONFIG_BT_GMAP_UGT_MULTIPLEX_SUPPORT is supported supported"); +#endif /* CONFIG_BT_GMAP_BGR_MULTIPLEX_SUPPORT && CONFIG_BT_GMAP_UGT_SINK_SUPPORT */ + +static ssize_t read_gmap_bgr_feat(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const uint8_t feat = (IS_ENABLED(CONFIG_BT_GMAP_BGR_MULTISINK_SUPPORT) ? + BT_GMAP_BGR_FEAT_MULTISINK : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_BGR_MULTIPLEX_SUPPORT) ? + BT_GMAP_BGR_FEAT_MULTIPLEX : 0); + + LOG_DBG("feat 0x%02X", feat); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &feat, sizeof(feat)); +} +#endif /* CONFIG_BT_GMAP_BGR */ + +BT_GATT_SERVICE_DEFINE(gmas, BT_GATT_PRIMARY_SERVICE(BT_UUID_GMAS), + BT_AUDIO_CHRC(BT_UUID_GMAP_ROLE, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, read_gmap_role, NULL, NULL), +#if defined(CONFIG_BT_GMAP_UGG) + BT_AUDIO_CHRC(BT_UUID_GMAP_UGG_FEAT, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, read_gmap_ugg_feat, NULL, NULL), +#endif /* CONFIG_BT_GMAP_UGG */ +#if defined(CONFIG_BT_GMAP_UGT) + BT_AUDIO_CHRC(BT_UUID_GMAP_UGT_FEAT, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, read_gmap_ugt_feat, NULL, NULL), +#endif /* CONFIG_BT_GMAP_UGT */ +#if defined(CONFIG_BT_GMAP_BGS) + BT_AUDIO_CHRC(BT_UUID_GMAP_BGS_FEAT, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, read_gmap_bgs_feat, NULL, NULL), +#endif /* CONFIG_BT_GMAP_BGS */ +#if defined(CONFIG_BT_GMAP_BGR) + BT_AUDIO_CHRC(BT_UUID_GMAP_BGR_FEAT, BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, read_gmap_bgr_feat, NULL, NULL) +#endif /* CONFIG_BT_GMAP_BGR */ + +); diff --git a/subsys/bluetooth/audio/shell/CMakeLists.txt b/subsys/bluetooth/audio/shell/CMakeLists.txt index 43b4cd879505b96..a4533831444c7f5 100644 --- a/subsys/bluetooth/audio/shell/CMakeLists.txt +++ b/subsys/bluetooth/audio/shell/CMakeLists.txt @@ -68,6 +68,7 @@ zephyr_library_sources_ifdef( zephyr_library_sources_ifdef( CONFIG_BT_BAP_STREAM bap.c + gmap.c ) zephyr_library_sources_ifdef( CONFIG_BT_BAP_SCAN_DELEGATOR diff --git a/subsys/bluetooth/audio/shell/audio.h b/subsys/bluetooth/audio/shell/audio.h index 4abc62aa095745a..94ffc52a6ce2f67 100644 --- a/subsys/bluetooth/audio/shell/audio.h +++ b/subsys/bluetooth/audio/shell/audio.h @@ -38,6 +38,9 @@ size_t cap_acceptor_ad_data_add(struct bt_data data[], size_t data_size, bool di #include #include +const struct named_lc3_preset *gmap_get_named_preset(bool is_unicast, enum bt_audio_dir dir, + const char *preset_arg); + struct named_lc3_preset { const char *name; struct bt_bap_lc3_preset preset; diff --git a/subsys/bluetooth/audio/shell/bap.c b/subsys/bluetooth/audio/shell/bap.c index 18c154aed0ac571..6da303965931c7b 100644 --- a/subsys/bluetooth/audio/shell/bap.c +++ b/subsys/bluetooth/audio/shell/bap.c @@ -24,21 +24,24 @@ #include #include #include +#include #include #include "shell/bt.h" #include "audio.h" -#define LOCATION BT_AUDIO_LOCATION_FRONT_LEFT -#define CONTEXT BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA +#define LOCATION BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT +#define CONTEXT \ + BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA | \ + BT_AUDIO_CONTEXT_TYPE_GAME #if defined(CONFIG_BT_BAP_UNICAST) struct unicast_stream unicast_streams[CONFIG_BT_MAX_CONN * (UNICAST_SERVER_STREAM_COUNT + UNICAST_CLIENT_STREAM_COUNT)]; -static const struct bt_codec_qos_pref qos_pref = BT_CODEC_QOS_PREF(true, BT_GAP_LE_PHY_2M, 0u, 60u, - 20000u, 40000u, 20000u, 40000u); +static const struct bt_codec_qos_pref qos_pref = + BT_CODEC_QOS_PREF(true, BT_GAP_LE_PHY_2M, 0u, 60u, 10000u, 40000u, 10000u, 40000u); #if defined(CONFIG_BT_BAP_UNICAST_CLIENT) struct bt_bap_unicast_group *default_unicast_group; @@ -346,7 +349,8 @@ void sdu_sent_cb(struct bt_bap_stream *stream) } #endif /* CONFIG_LIBLC3 && CONFIG_BT_AUDIO_TX */ -static const struct named_lc3_preset *get_named_preset(bool is_unicast, const char *preset_arg) +static const struct named_lc3_preset *get_named_preset(bool is_unicast, enum bt_audio_dir dir, + const char *preset_arg) { if (is_unicast) { for (size_t i = 0U; i < ARRAY_SIZE(lc3_unicast_presets); i++) { @@ -362,6 +366,10 @@ static const struct named_lc3_preset *get_named_preset(bool is_unicast, const ch } } + if (IS_ENABLED(CONFIG_BT_GMAP_UGG) || IS_ENABLED(CONFIG_BT_GMAP_BGS)) { + return gmap_get_named_preset(is_unicast, dir, preset_arg); + } + return NULL; } @@ -579,11 +587,9 @@ static int lc3_release(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp return 0; } -static struct bt_codec lc3_codec = BT_CODEC_LC3(BT_CODEC_LC3_FREQ_ANY, - BT_CODEC_LC3_DURATION_ANY, - BT_CODEC_LC3_CHAN_COUNT_SUPPORT(1, 2), 30, 240, 2, - (BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | - BT_AUDIO_CONTEXT_TYPE_MEDIA)); +static struct bt_codec lc3_codec = + BT_CODEC_LC3(BT_CODEC_LC3_FREQ_ANY, BT_CODEC_LC3_DURATION_ANY, + BT_CODEC_LC3_CHAN_COUNT_SUPPORT(1, 2), 30, 240, 2, CONTEXT); static const struct bt_bap_unicast_server_cb unicast_server_cb = { .config = lc3_config, @@ -922,6 +928,7 @@ static int cmd_config(const struct shell *sh, size_t argc, char *argv[]) struct unicast_stream *uni_stream; struct bt_bap_stream *bap_stream; struct bt_bap_ep *ep = NULL; + enum bt_audio_dir dir; unsigned long index; uint8_t conn_index; int err = 0; @@ -955,6 +962,7 @@ static int cmd_config(const struct shell *sh, size_t argc, char *argv[]) #if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 } else if (!strcmp(argv[1], "sink")) { + dir = BT_AUDIO_DIR_SINK; ep = snks[conn_index][index]; named_preset = default_sink_preset; @@ -962,6 +970,7 @@ static int cmd_config(const struct shell *sh, size_t argc, char *argv[]) #if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 } else if (!strcmp(argv[1], "source")) { + dir = BT_AUDIO_DIR_SOURCE; ep = srcs[conn_index][index]; named_preset = default_source_preset; @@ -1009,7 +1018,7 @@ static int cmd_config(const struct shell *sh, size_t argc, char *argv[]) if (argc > i) { arg = argv[++i]; - named_preset = get_named_preset(true, arg); + named_preset = get_named_preset(true, dir, arg); if (named_preset == NULL) { shell_error(sh, "Unable to parse named_preset %s", arg); return -ENOEXEC; @@ -1329,14 +1338,18 @@ static int cmd_stop(const struct shell *sh, size_t argc, char *argv[]) static int cmd_preset(const struct shell *sh, size_t argc, char *argv[]) { const struct named_lc3_preset *named_preset; + enum bt_audio_dir dir; bool unicast = true; if (!strcmp(argv[1], "sink")) { + dir = BT_AUDIO_DIR_SINK; named_preset = default_sink_preset; } else if (!strcmp(argv[1], "source")) { + dir = BT_AUDIO_DIR_SOURCE; named_preset = default_source_preset; } else if (!strcmp(argv[1], "broadcast")) { unicast = false; + dir = BT_AUDIO_DIR_SOURCE; named_preset = default_broadcast_source_preset; } else { @@ -1345,7 +1358,7 @@ static int cmd_preset(const struct shell *sh, size_t argc, char *argv[]) } if (argc > 2) { - named_preset = get_named_preset(unicast, argv[2]); + named_preset = get_named_preset(unicast, dir, argv[2]); if (named_preset == NULL) { shell_error(sh, "Unable to parse named_preset %s", argv[2]); return -ENOEXEC; @@ -1881,7 +1894,7 @@ static int cmd_create_broadcast(const struct shell *sh, size_t argc, i++; arg = argv[i]; - named_preset = get_named_preset(false, arg); + named_preset = get_named_preset(false, BT_AUDIO_DIR_SOURCE, arg); if (named_preset == NULL) { shell_error(sh, "Unable to parse named_preset %s", arg); @@ -2563,6 +2576,22 @@ static ssize_t connectable_ad_data_add(struct bt_data *data_array, true); } + if (IS_ENABLED(CONFIG_BT_GMAP)) { + const uint8_t role = (IS_ENABLED(CONFIG_BT_GMAP_UGG) ? BT_GMAP_ROLE_UGG : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_UGT) ? BT_GMAP_ROLE_UGT : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_BGS) ? BT_GMAP_ROLE_BGS : 0) | + (IS_ENABLED(CONFIG_BT_GMAP_BGR) ? BT_GMAP_ROLE_BGR : 0); + static const uint8_t ad_gmap[3] = { + BT_UUID_16_ENCODE(BT_UUID_GMAS_VAL), role, + }; + + __ASSERT(data_array_size > ad_len, "No space for ad_gmap"); + data_array[ad_len].type = BT_DATA_SVC_DATA16; + data_array[ad_len].data_len = ARRAY_SIZE(ad_gmap); + data_array[ad_len].data = &ad_gmap[0]; + ad_len++; + } + if (ARRAY_SIZE(ad_ext_uuid16) > 0) { size_t uuid16_size; diff --git a/subsys/bluetooth/audio/shell/gmap.c b/subsys/bluetooth/audio/shell/gmap.c new file mode 100644 index 000000000000000..b047522bbd42f51 --- /dev/null +++ b/subsys/bluetooth/audio/shell/gmap.c @@ -0,0 +1,940 @@ +/** @file + * @brief Bluetooth Gaming Audio Profile shell + * + */ + +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "shell/bt.h" +#include "audio.h" + +#define UNICAST_SINK_SUPPORTED (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0) +#define UNICAST_SRC_SUPPORTED (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0) + +#define LOCATION BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT +#define CONTEXT \ + BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | BT_AUDIO_CONTEXT_TYPE_MEDIA | \ + BT_AUDIO_CONTEXT_TYPE_GAME + +#define GMAP_AC_MAX_CONN 2U +#define GMAP_AC_MAX_SNK (2U * GMAP_AC_MAX_CONN) +#define GMAP_AC_MAX_SRC (2U * GMAP_AC_MAX_CONN) +#define GMAP_AC_MAX_PAIR MAX(GMAP_AC_MAX_SNK, GMAP_AC_MAX_SRC) +#define GMAP_AC_MAX_STREAM (GMAP_AC_MAX_SNK + GMAP_AC_MAX_SRC) + +static void gmap_discover_cb(struct bt_conn *conn, int err, enum bt_gmap_role role, + enum bt_gmap_ugg_feat ugg_feat, enum bt_gmap_ugt_feat ugt_feat, + enum bt_gmap_bgs_feat bgs_feat, enum bt_gmap_bgr_feat bgr_feat) +{ + if (err != 0) { + shell_error(ctx_shell, "gmap discovery (err %d)", err); + return; + } + + shell_print(ctx_shell, + "gmap discovered for conn %p:\n\trole 0x%02x\n\tugg_feat 0x%02x\n\tugt_feat " + "0x%02x\n\tbgs_feat 0x%02x\n\tbgr_feat 0x%02x", + conn, role, ugg_feat, ugt_feat, bgs_feat, bgr_feat); +} + +static const struct bt_gmap_cb gmap_cb = { + .discover = gmap_discover_cb, +}; + +static int cmd_gmap_init(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + if (!ctx_shell) { + ctx_shell = sh; + } + + err = bt_gmap_cb_register(&gmap_cb); + if (err != 0) { + shell_error(sh, "bt_gmap_cb_register (err %d)", err); + } + + return err; +} + +static int cmd_gmap_discover(const struct shell *sh, size_t argc, char **argv) +{ + int err; + + if (default_conn == NULL) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!ctx_shell) { + ctx_shell = sh; + } + + err = bt_gmap_discover(default_conn); + if (err != 0) { + shell_error(sh, "bt_gmap_discover (err %d)", err); + } + + return err; +} + +#if defined(CONFIG_BT_GMAP_UGG) || defined(CONFIG_BT_GMAP_BGS) +struct gmap_ac_param { + char *name; + size_t conn_cnt; + size_t snk_cnt[GMAP_AC_MAX_CONN]; + size_t src_cnt[GMAP_AC_MAX_CONN]; + size_t snk_chan_cnt; +}; + +static struct named_lc3_preset gmap_unicast_snk_presets[] = { + {"32_1_gr", BT_GMAP_LC3_PRESET_32_1_GR(LOCATION, CONTEXT)}, + {"32_2_gr", BT_GMAP_LC3_PRESET_32_2_GR(LOCATION, CONTEXT)}, + {"48_1_gr", BT_GMAP_LC3_PRESET_48_1_GR(LOCATION, CONTEXT)}, + {"48_2_gr", BT_GMAP_LC3_PRESET_48_2_GR(LOCATION, CONTEXT)}, + {"48_3_gr", BT_GMAP_LC3_PRESET_48_3_GR(LOCATION, CONTEXT)}, + {"48_4_gr", BT_GMAP_LC3_PRESET_48_4_GR(LOCATION, CONTEXT)}, +}; + +static struct named_lc3_preset gmap_unicast_src_presets[] = { + {"32_1_gs", BT_GMAP_LC3_PRESET_32_1_GS(LOCATION, CONTEXT)}, + {"32_2_gs", BT_GMAP_LC3_PRESET_32_2_GS(LOCATION, CONTEXT)}, + {"48_1_gs", BT_GMAP_LC3_PRESET_48_1_GS(LOCATION, CONTEXT)}, + {"48_2_gs", BT_GMAP_LC3_PRESET_48_2_GS(LOCATION, CONTEXT)}, +}; + +static struct named_lc3_preset gmap_broadcast_presets[] = { + {"48_1_g", BT_GMAP_LC3_PRESET_48_1_G(LOCATION, CONTEXT)}, + {"48_2_g", BT_GMAP_LC3_PRESET_48_2_G(LOCATION, CONTEXT)}, + {"48_3_g", BT_GMAP_LC3_PRESET_48_3_G(LOCATION, CONTEXT)}, + {"48_4_g", BT_GMAP_LC3_PRESET_48_4_G(LOCATION, CONTEXT)}, +}; + +const struct named_lc3_preset *gmap_get_named_preset(bool is_unicast, enum bt_audio_dir dir, + const char *preset_arg) +{ + if (is_unicast) { + if (dir == BT_AUDIO_DIR_SINK) { + for (size_t i = 0U; i < ARRAY_SIZE(gmap_unicast_snk_presets); i++) { + if (!strcmp(preset_arg, gmap_unicast_snk_presets[i].name)) { + return &gmap_unicast_snk_presets[i]; + } + } + } else if (dir == BT_AUDIO_DIR_SOURCE) { + for (size_t i = 0U; i < ARRAY_SIZE(gmap_unicast_src_presets); i++) { + if (!strcmp(preset_arg, gmap_unicast_src_presets[i].name)) { + return &gmap_unicast_src_presets[i]; + } + } + } + } else { + + for (size_t i = 0U; i < ARRAY_SIZE(gmap_broadcast_presets); i++) { + if (!strcmp(preset_arg, gmap_broadcast_presets[i].name)) { + return &gmap_broadcast_presets[i]; + } + } + } + + return NULL; +} +#endif /* CONFIG_BT_GMAP_UGG || CONFIG_BT_GMAP_BGS */ + +#if defined(CONFIG_BT_GMAP_UGG) +static void populate_connected_conns(struct bt_conn *conn, void *data) +{ + struct bt_conn **connected_conns = (struct bt_conn **)data; + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err != 0) { + shell_error(ctx_shell, "Failed to get conn info for %p: %d", conn, err); + return; + } + + if (info.state != BT_CONN_STATE_CONNECTED) { + /* skip */ + return; + } + + if (info.role != BT_CONN_ROLE_CENTRAL) { + /* Skip as we only want connections where we are central as per spec requirements */ + return; + } + + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) { + if (connected_conns[i] == NULL) { + connected_conns[i] = conn; + return; + } + } +} + +static int gmap_ac_create_unicast_group(const struct gmap_ac_param *param, + struct unicast_stream *snk_uni_streams[], size_t snk_cnt, + struct unicast_stream *src_uni_streams[], size_t src_cnt) +{ + struct bt_bap_unicast_group_stream_param snk_group_stream_params[GMAP_AC_MAX_SNK] = {0}; + struct bt_bap_unicast_group_stream_param src_group_stream_params[GMAP_AC_MAX_SRC] = {0}; + struct bt_bap_unicast_group_stream_pair_param pair_params[GMAP_AC_MAX_PAIR] = {0}; + struct bt_bap_unicast_group_param group_param = {0}; + struct bt_codec_qos *snk_qos[GMAP_AC_MAX_SNK]; + struct bt_codec_qos *src_qos[GMAP_AC_MAX_SRC]; + size_t snk_stream_cnt = 0U; + size_t src_stream_cnt = 0U; + size_t pair_cnt = 0U; + + for (size_t i = 0U; i < snk_cnt; i++) { + snk_qos[i] = &snk_uni_streams[i]->qos; + } + + for (size_t i = 0U; i < src_cnt; i++) { + src_qos[i] = &src_uni_streams[i]->qos; + } + + /* Create Group + * + * First setup the individual stream parameters and then match them in pairs by connection + * and direction + */ + for (size_t i = 0U; i < snk_cnt; i++) { + snk_group_stream_params[i].qos = snk_qos[i]; + snk_group_stream_params[i].stream = &snk_uni_streams[i]->stream.bap_stream; + } + for (size_t i = 0U; i < src_cnt; i++) { + src_group_stream_params[i].qos = src_qos[i]; + src_group_stream_params[i].stream = &src_uni_streams[i]->stream.bap_stream; + } + + for (size_t i = 0U; i < param->conn_cnt; i++) { + for (size_t j = 0; j < MAX(param->snk_cnt[i], param->src_cnt[i]); j++) { + if (param->snk_cnt[i] > j) { + pair_params[pair_cnt].tx_param = + &snk_group_stream_params[snk_stream_cnt++]; + } else { + pair_params[pair_cnt].tx_param = NULL; + } + + if (param->src_cnt[i] > j) { + pair_params[pair_cnt].rx_param = + &src_group_stream_params[src_stream_cnt++]; + } else { + pair_params[pair_cnt].rx_param = NULL; + } + + pair_cnt++; + } + } + + group_param.packing = BT_ISO_PACKING_SEQUENTIAL; + group_param.params = pair_params; + group_param.params_count = pair_cnt; + + return bt_bap_unicast_group_create(&group_param, &default_unicast_group); +} + +static void codec_data_set_chan_alloc(struct bt_codec_data *data, enum bt_audio_location loc) +{ + const uint32_t loc_32 = loc; + + data->data.type = BT_CODEC_CONFIG_LC3_CHAN_ALLOC; + data->data.data_len = sizeof(loc_32); + sys_put_le32(loc_32, data->value); +} + +static int codec_set_chan_alloc(struct bt_codec *codec, enum bt_audio_location loc) +{ + for (size_t i = 0U; i < codec->data_count; i++) { + struct bt_codec_data *data = &codec->data[i]; + + /* Overwrite the location value */ + if (data->data.type == BT_CODEC_CONFIG_LC3_CHAN_ALLOC) { + codec_data_set_chan_alloc(data, loc); + + return 0; + } + } + + /* Not found, add new if possible */ + if (codec->data_count < CONFIG_BT_CODEC_MAX_DATA_COUNT) { + struct bt_codec_data *data = &codec->data[codec->data_count++]; + + codec_data_set_chan_alloc(data, loc); + + return 0; + } + + return -ENOMEM; +} + +static int gmap_ac_cap_unicast_start(const struct gmap_ac_param *param, + struct bt_conn *connected_conns[], + struct unicast_stream *snk_uni_streams[], size_t snk_cnt, + struct unicast_stream *src_uni_streams[], size_t src_cnt) +{ + struct bt_cap_unicast_audio_start_stream_param stream_params[GMAP_AC_MAX_STREAM] = {0}; + struct bt_cap_unicast_audio_start_param start_param = {0}; + struct bt_cap_stream *snk_cap_streams[GMAP_AC_MAX_SNK] = {0}; + struct bt_cap_stream *src_cap_streams[GMAP_AC_MAX_SRC] = {0}; + struct bt_codec_qos *snk_qos[GMAP_AC_MAX_SNK] = {0}; + struct bt_codec_qos *src_qos[GMAP_AC_MAX_SRC] = {0}; + struct bt_codec *snk_codecs[GMAP_AC_MAX_SNK] = {0}; + struct bt_codec *src_codecs[GMAP_AC_MAX_SRC] = {0}; + struct bt_bap_ep *snk_eps[GMAP_AC_MAX_SNK] = {0}; + struct bt_bap_ep *src_eps[GMAP_AC_MAX_SRC] = {0}; + size_t snk_stream_cnt = 0U; + size_t src_stream_cnt = 0U; + size_t stream_cnt = 0U; + size_t snk_ep_cnt = 0U; + size_t src_ep_cnt = 0U; + + for (size_t i = 0U; i < param->conn_cnt; i++) { +#if UNICAST_SINK_SUPPORTED + for (size_t j = 0U; j < param->snk_cnt[i]; j++) { + snk_eps[snk_ep_cnt] = snks[bt_conn_index(connected_conns[i])][j]; + if (snk_eps[snk_ep_cnt] == NULL) { + shell_error(ctx_shell, "No sink[%zu][%zu] endpoint available", i, + j); + + return -ENOEXEC; + } + snk_ep_cnt++; + } +#endif /* UNICAST_SINK_SUPPORTED */ + +#if UNICAST_SRC_SUPPORTED + for (size_t j = 0U; j < param->src_cnt[i]; j++) { + src_eps[src_ep_cnt] = srcs[bt_conn_index(connected_conns[i])][j]; + if (src_eps[src_ep_cnt] == NULL) { + shell_error(ctx_shell, "No source[%zu][%zu] endpoint available", i, + j); + + return -ENOEXEC; + } + src_ep_cnt++; + } +#endif /* UNICAST_SRC_SUPPORTED > 0 */ + } + + if (snk_ep_cnt != snk_cnt) { + shell_error(ctx_shell, "Sink endpoint and stream count mismatch: %zu != %zu", + snk_ep_cnt, snk_cnt); + + return -ENOEXEC; + } + + if (src_ep_cnt != src_cnt) { + shell_error(ctx_shell, "Source endpoint and stream count mismatch: %zu != %zu", + src_ep_cnt, src_cnt); + + return -ENOEXEC; + } + + /* Setup arrays of parameters based on the preset for easier access. This also copies the + * preset so that we can modify them (e.g. update the metadata) + */ + for (size_t i = 0U; i < snk_cnt; i++) { + snk_cap_streams[i] = &snk_uni_streams[i]->stream; + snk_qos[i] = &snk_uni_streams[i]->qos; + snk_codecs[i] = &snk_uni_streams[i]->codec; + } + + for (size_t i = 0U; i < src_cnt; i++) { + src_cap_streams[i] = &src_uni_streams[i]->stream; + src_qos[i] = &src_uni_streams[i]->qos; + src_codecs[i] = &src_uni_streams[i]->codec; + } + + /* CAP Start */ + for (size_t i = 0U; i < param->conn_cnt; i++) { + for (size_t j = 0U; j < param->snk_cnt[i]; j++) { + struct bt_cap_unicast_audio_start_stream_param *stream_param = + &stream_params[stream_cnt]; + + stream_param->member.member = connected_conns[i]; + stream_param->codec = snk_codecs[snk_stream_cnt]; + stream_param->ep = snk_eps[snk_stream_cnt]; + stream_param->qos = snk_qos[snk_stream_cnt]; + stream_param->stream = snk_cap_streams[snk_stream_cnt]; + + snk_stream_cnt++; + stream_cnt++; + + if (param->conn_cnt > 1) { + const int err = codec_set_chan_alloc(stream_param->codec, BIT(i)); + + if (err != 0) { + shell_error(ctx_shell, + "Failed to set channel allocation for " + "snk[%zu][%zu]: %d", + i, j, err); + + return err; + } + } + } + + for (size_t j = 0U; j < param->src_cnt[i]; j++) { + struct bt_cap_unicast_audio_start_stream_param *stream_param = + &stream_params[stream_cnt]; + + stream_param->member.member = connected_conns[i]; + stream_param->codec = src_codecs[src_stream_cnt]; + stream_param->ep = src_eps[src_stream_cnt]; + stream_param->qos = src_qos[src_stream_cnt]; + stream_param->stream = src_cap_streams[src_stream_cnt]; + + src_stream_cnt++; + stream_cnt++; + + if (param->conn_cnt > 1) { + const int err = codec_set_chan_alloc(stream_param->codec, BIT(i)); + + if (err != 0) { + shell_error(ctx_shell, + "Failed to set channel allocation for " + "src[%zu][%zu]: %d", + i, j, err); + + return err; + } + } + } + } + + start_param.stream_params = stream_params; + start_param.count = stream_cnt; + start_param.type = BT_CAP_SET_TYPE_AD_HOC; + + return bt_cap_initiator_unicast_audio_start(&start_param, default_unicast_group); +} + +static int gmap_ac_unicast(const struct shell *sh, size_t argc, char **argv, + const struct gmap_ac_param *param) +{ + /* Allocate params large enough for any params, but only use what is required */ + struct bt_conn *connected_conns[GMAP_AC_MAX_CONN] = {0}; + struct unicast_stream *snk_uni_streams[GMAP_AC_MAX_SNK]; + struct unicast_stream *src_uni_streams[GMAP_AC_MAX_SRC]; + const struct named_lc3_preset *snk_named_preset = NULL; + const struct named_lc3_preset *src_named_preset = NULL; + size_t conn_avail_cnt; + size_t snk_cnt = 0; + size_t src_cnt = 0; + int err; + + if (default_unicast_group != NULL) { + shell_error(sh, "Unicast Group already exist, please delete first"); + return -ENOEXEC; + } + + if (param->conn_cnt > GMAP_AC_MAX_CONN) { + shell_error(sh, "Invalid conn_cnt: %zu", param->conn_cnt); + return -ENOEXEC; + } + + for (size_t i = 0; i < param->conn_cnt; i++) { + /* Verify conn values */ + if (param->snk_cnt[i] > GMAP_AC_MAX_SNK) { + shell_error(sh, "Invalid conn_snk_cnt[%zu]: %zu", i, param->snk_cnt[i]); + return -ENOEXEC; + } + + if (param->src_cnt[i] > GMAP_AC_MAX_SRC) { + shell_error(sh, "Invalid conn_src_cnt[%zu]: %zu", i, param->src_cnt[i]); + return -ENOEXEC; + } + } + + /* Populate the array of connected connections */ + bt_conn_foreach(BT_CONN_TYPE_LE, populate_connected_conns, (void *)connected_conns); + for (conn_avail_cnt = 0; conn_avail_cnt < ARRAY_SIZE(connected_conns); conn_avail_cnt++) { + if (connected_conns[conn_avail_cnt] == NULL) { + break; + } + } + + if (conn_avail_cnt < param->conn_cnt) { + shell_error(sh, + "Only %zu/%u connected devices, please connect additional devices for " + "this audio configuration", + conn_avail_cnt, param->conn_cnt); + return -ENOEXEC; + } + + /* Set all endpoints from multiple connections in a single array, and verify that the known + * endpoints matches the audio configuration + */ + for (size_t i = 0U; i < param->conn_cnt; i++) { + for (size_t j = 0U; j < param->snk_cnt[i]; j++) { + snk_cnt++; + } + + for (size_t j = 0U; j < param->src_cnt[i]; j++) { + src_cnt++; + } + } + + if (snk_cnt > 0U) { + snk_named_preset = gmap_get_named_preset(true, BT_AUDIO_DIR_SINK, argv[1]); + if (snk_named_preset == NULL) { + shell_error(sh, "Unable to parse snk_named_preset %s", argv[1]); + return -ENOEXEC; + } + } + + if (src_cnt > 0U) { + const char *preset_arg = argc > 2 ? argv[2] : argv[1]; + + src_named_preset = gmap_get_named_preset(true, BT_AUDIO_DIR_SOURCE, preset_arg); + if (src_named_preset == NULL) { + shell_error(sh, "Unable to parse src_named_preset %s", argv[1]); + return -ENOEXEC; + } + } + + if (!ctx_shell) { + ctx_shell = sh; + } + + /* Setup arrays of parameters based on the preset for easier access. This also copies the + * preset so that we can modify them (e.g. update the metadata) + */ + for (size_t i = 0U; i < snk_cnt; i++) { + snk_uni_streams[i] = &unicast_streams[i]; + + if (unicast_streams[i].stream.bap_stream.conn != NULL) { + shell_error(sh, "unicast_streams[%zu] already in use", i); + return -ENOEXEC; + } + + copy_unicast_stream_preset(snk_uni_streams[i], snk_named_preset); + + /* Some audio configuration requires multiple sink channels, + * so multiply the SDU based on the channel count + */ + snk_uni_streams[i]->qos.sdu *= param->snk_chan_cnt; + } + + for (size_t i = 0U; i < src_cnt; i++) { + src_uni_streams[i] = &unicast_streams[i + snk_cnt]; + + if (unicast_streams[i].stream.bap_stream.conn != NULL) { + shell_error(sh, "unicast_streams[%zu] already in use", i + snk_cnt); + return -ENOEXEC; + } + + copy_unicast_stream_preset(src_uni_streams[i], src_named_preset); + } + + err = gmap_ac_create_unicast_group(param, snk_uni_streams, snk_cnt, src_uni_streams, + src_cnt); + if (err != 0) { + shell_error(sh, "Failed to create group: %d", err); + + return -ENOEXEC; + } + + shell_print(sh, "Starting %zu streams for %s", snk_cnt + src_cnt, param->name); + err = gmap_ac_cap_unicast_start(param, connected_conns, snk_uni_streams, snk_cnt, + src_uni_streams, src_cnt); + if (err != 0) { + shell_error(sh, "Failed to start unicast audio: %d", err); + + err = bt_bap_unicast_group_delete(default_unicast_group); + if (err != 0) { + shell_error(sh, "Failed to delete group: %d", err); + } else { + default_unicast_group = NULL; + } + + return -ENOEXEC; + } + + return 0; +} + +#if UNICAST_SINK_SUPPORTED +static int cmd_gmap_ac_1(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_1", + .conn_cnt = 1, + .snk_cnt = {1}, + .src_cnt = {0}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* UNICAST_SINK_SUPPORTED */ + +#if UNICAST_SRC_SUPPORTED +static int cmd_gmap_ac_2(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_2", + .conn_cnt = 1, + .snk_cnt = {0}, + .src_cnt = {1}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* UNICAST_SRC_SUPPORTED */ + +#if UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED +static int cmd_gmap_ac_3(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_3", + .conn_cnt = 1, + .snk_cnt = {1}, + .src_cnt = {1}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED */ + +#if UNICAST_SINK_SUPPORTED +static int cmd_gmap_ac_4(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_4", + .conn_cnt = 1, + .snk_cnt = {1}, + .src_cnt = {0}, + .snk_chan_cnt = 2, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* UNICAST_SINK_SUPPORTED */ + +#if UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED +static int cmd_gmap_ac_5(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_5", + .conn_cnt = 1, + .snk_cnt = {1}, + .src_cnt = {1}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED */ + +#if UNICAST_SINK_SUPPORTED +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 +static int cmd_gmap_ac_6_i(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_6_I", + .conn_cnt = 1, + .snk_cnt = {2}, + .src_cnt = {0}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 */ + +#if CONFIG_BT_MAX_CONN >= 2 +static int cmd_gmap_ac_6_ii(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_6_II", + .conn_cnt = 2, + .snk_cnt = {1, 1}, + .src_cnt = {0, 0}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_MAX_CONN >= 2 */ +#endif /* UNICAST_SINK_SUPPORTED */ + +#if UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED +#if CONFIG_BT_MAX_CONN >= 2 +static int cmd_gmap_ac_7_ii(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_7_II", + .conn_cnt = 2, + .snk_cnt = {1, 0}, + .src_cnt = {0, 1}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_MAX_CONN >= 2 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 +static int cmd_gmap_ac_8_i(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_8_I", + .conn_cnt = 1, + .snk_cnt = {2}, + .src_cnt = {1}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 */ + +#if CONFIG_BT_MAX_CONN >= 2 +static int cmd_gmap_ac_8_ii(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_8_II", + .conn_cnt = 2, + .snk_cnt = {1, 1}, + .src_cnt = {1, 0}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_MAX_CONN >= 2 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 && CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 1 +static int cmd_gmap_ac_11_i(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_11_I", + .conn_cnt = 1, + .snk_cnt = {2}, + .src_cnt = {2}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 && \ + * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 1 \ + */ + +#if CONFIG_BT_MAX_CONN >= 2 +static int cmd_gmap_ac_11_ii(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_11_II", + .conn_cnt = 2, + .snk_cnt = {1, 1}, + .src_cnt = {1, 1}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_unicast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_MAX_CONN >= 2 */ +#endif /* UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED */ +#endif /* CONFIG_BT_GMAP_UGG */ + +#if defined(CONFIG_BT_GMAP_BGS) + +static int gmap_ac_broadcast(const struct shell *sh, size_t argc, char **argv, + const struct gmap_ac_param *param) +{ + /* TODO: Use CAP API when updated */ + struct bt_codec_data loc_data = BT_CODEC_DATA(BT_CODEC_CONFIG_LC3_CHAN_ALLOC, LOCATION); + struct bt_codec_data right_data = + BT_CODEC_DATA(BT_CODEC_CONFIG_LC3_CHAN_ALLOC, BT_AUDIO_LOCATION_FRONT_RIGHT); + struct bt_codec_data left_data = + BT_CODEC_DATA(BT_CODEC_CONFIG_LC3_CHAN_ALLOC, BT_AUDIO_LOCATION_FRONT_LEFT); + struct bt_bap_broadcast_source_stream_param stream_params[GMAP_AC_MAX_SRC] = {0}; + struct bt_bap_broadcast_source_subgroup_param subgroup_param = {0}; + struct bt_bap_broadcast_source_create_param create_param = {0}; + const struct named_lc3_preset *named_preset; + const size_t stream_cnt = param->src_cnt[0]; + struct bt_le_ext_adv *adv; + int err; + + if (default_source.bap_source != NULL) { + shell_error(sh, "Broadcast Source already created, please delete first"); + return -ENOEXEC; + } + + adv = adv_sets[selected_adv]; + if (adv == NULL) { + shell_error(sh, "Extended advertising set is NULL"); + return -ENOEXEC; + } + + named_preset = gmap_get_named_preset(false, BT_AUDIO_DIR_SOURCE, argv[1]); + if (named_preset == NULL) { + shell_error(sh, "Unable to parse named_preset %s", argv[1]); + return -ENOEXEC; + } + + copy_broadcast_source_preset(&default_source, named_preset); + default_source.qos.sdu *= param->snk_chan_cnt; + + for (size_t i = 0U; i < stream_cnt; i++) { + stream_params[i].stream = &broadcast_source_streams[i].stream.bap_stream; + stream_params[i].data_count = 1U; + + if (stream_cnt == 1U) { + stream_params[i].data = &loc_data; + } else if (i == 0U) { + stream_params[i].data = &left_data; + } else if (i == 1U) { + stream_params[i].data = &right_data; + } + } + + subgroup_param.params_count = stream_cnt; + subgroup_param.params = stream_params; + subgroup_param.codec = &default_source.codec; + create_param.params_count = 1U; + create_param.params = &subgroup_param; + create_param.qos = &default_source.qos; + + err = bt_bap_broadcast_source_create(&create_param, &default_source.bap_source); + if (err != 0) { + shell_error(sh, "Failed to create broadcast source: %d", err); + return -ENOEXEC; + } + + shell_print(sh, "Broadcast source for %s created. Start via `bap start_broadcast`", + param->name); + + return 0; +} + +static int cmd_gmap_ac_12(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_12", + .conn_cnt = 0, + .snk_cnt = {0}, + .src_cnt = {1}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_broadcast(sh, argc, argv, ¶m); +} + +#if CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT > 1 +static int cmd_gmap_ac_13(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_13", + .conn_cnt = 0, + .snk_cnt = {0}, + .src_cnt = {2}, + .snk_chan_cnt = 1, + }; + + return gmap_ac_broadcast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT > 1 */ + +static int cmd_gmap_ac_14(const struct shell *sh, size_t argc, char **argv) +{ + const struct gmap_ac_param param = { + .name = "AC_13", + .conn_cnt = 0, + .snk_cnt = {0}, + .src_cnt = {1}, + .snk_chan_cnt = 2, + }; + + return gmap_ac_broadcast(sh, argc, argv, ¶m); +} +#endif /* CONFIG_BT_GMAP_BGS */ + +static int cmd_gmap(const struct shell *sh, size_t argc, char **argv) +{ + if (argc > 1) { + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + } else { + shell_error(sh, "%s missing subcomand", argv[0]); + } + + return -ENOEXEC; +} + +#define HELP_NONE "[none]" + +SHELL_STATIC_SUBCMD_SET_CREATE( + gmap_cmds, SHELL_CMD_ARG(init, NULL, HELP_NONE, cmd_gmap_init, 1, 0), + SHELL_CMD_ARG(discover, NULL, HELP_NONE, cmd_gmap_discover, 1, 0), +#if defined(CONFIG_BT_GMAP_UGG) +#if UNICAST_SINK_SUPPORTED + SHELL_CMD_ARG(ac_1, NULL, "", cmd_gmap_ac_1, 2, 0), +#endif /* UNICAST_SINK_SUPPORTED */ +#if UNICAST_SRC_SUPPORTED + SHELL_CMD_ARG(ac_2, NULL, "", cmd_gmap_ac_2, 2, 0), +#endif /* UNICAST_SRC_SUPPORTED */ +#if UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED + SHELL_CMD_ARG(ac_3, NULL, " ", cmd_gmap_ac_3, 3, 0), +#endif /* UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED */ +#if UNICAST_SINK_SUPPORTED + SHELL_CMD_ARG(ac_4, NULL, "", cmd_gmap_ac_4, 2, 0), +#endif /* UNICAST_SINK_SUPPORTED */ +#if UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED + SHELL_CMD_ARG(ac_5, NULL, " ", cmd_gmap_ac_5, 3, 0), +#endif /* UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED */ +#if UNICAST_SINK_SUPPORTED +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 + SHELL_CMD_ARG(ac_6_i, NULL, "", cmd_gmap_ac_6_i, 2, 0), +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 */ +#if CONFIG_BT_MAX_CONN >= 2 + SHELL_CMD_ARG(ac_6_ii, NULL, "", cmd_gmap_ac_6_ii, 2, 0), +#endif /* CONFIG_BT_MAX_CONN >= 2 */ +#endif /* UNICAST_SINK_SUPPORTED */ +#if UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED +#if CONFIG_BT_MAX_CONN >= 2 + SHELL_CMD_ARG(ac_7_ii, NULL, " ", cmd_gmap_ac_7_ii, 3, 0), +#endif /* CONFIG_BT_MAX_CONN >= 2 */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 + SHELL_CMD_ARG(ac_8_i, NULL, " ", cmd_gmap_ac_8_i, 3, 0), +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 */ +#if CONFIG_BT_MAX_CONN >= 2 + SHELL_CMD_ARG(ac_8_ii, NULL, " ", cmd_gmap_ac_8_ii, 3, 0), +#endif /* CONFIG_BT_MAX_CONN >= 2 */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 && CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 1 + SHELL_CMD_ARG(ac_11_i, NULL, " ", cmd_gmap_ac_11_i, 3, 0), +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1 && \ + * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 1 \ + */ +#if CONFIG_BT_MAX_CONN >= 2 + SHELL_CMD_ARG(ac_11_ii, NULL, " ", cmd_gmap_ac_11_ii, 3, 0), +#endif /* CONFIG_BT_MAX_CONN >= 2 */ +#endif /* UNICAST_SINK_SUPPORTED && UNICAST_SRC_SUPPORTED */ +#endif /* CONFIG_BT_GMAP_UGG */ +#if defined(CONFIG_BT_GMAP_BGS) + SHELL_CMD_ARG(ac_12, NULL, "", cmd_gmap_ac_12, 2, 0), +#if CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT > 1 + SHELL_CMD_ARG(ac_13, NULL, "", cmd_gmap_ac_13, 2, 0), +#endif /* CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT > 1 */ + SHELL_CMD_ARG(ac_14, NULL, "", cmd_gmap_ac_14, 2, 0), +#endif /* CONFIG_BT_GMAP_BGS */ + SHELL_SUBCMD_SET_END); + +SHELL_CMD_ARG_REGISTER(gmap, &gmap_cmds, "Bluetooth gmap client shell commands", cmd_gmap, 1, 1); diff --git a/subsys/bluetooth/shell/bt.c b/subsys/bluetooth/shell/bt.c index 76d836e9bc3e553..39a5bf438a358d7 100644 --- a/subsys/bluetooth/shell/bt.c +++ b/subsys/bluetooth/shell/bt.c @@ -602,10 +602,13 @@ static void connected(struct bt_conn *conn, uint8_t err) shell_print(ctx_shell, "Connected: %s", addr); - if (!default_conn) { - default_conn = bt_conn_ref(conn); + /* Set the newest conn as the default */ + if (default_conn) { + bt_conn_unref(default_conn); } + default_conn = bt_conn_ref(conn); + done: /* clear connection reference for sec mode 3 pairing */ if (pairing_conn) { @@ -2625,8 +2628,10 @@ static int cmd_connect_le(const struct shell *sh, size_t argc, char *argv[]) BT_GAP_SCAN_FAST_INTERVAL, BT_GAP_SCAN_FAST_INTERVAL); - err = bt_conn_le_create(&addr, create_params, BT_LE_CONN_PARAM_DEFAULT, - &conn); + err = bt_conn_le_create( + &addr, create_params, + BT_LE_CONN_PARAM(BT_GAP_INIT_CONN_INT_MIN, BT_GAP_INIT_CONN_INT_MIN, 0, 400), + &conn); if (err) { shell_error(sh, "Connection failed (%d)", err); return -ENOEXEC; diff --git a/tests/bluetooth/shell/audio.conf b/tests/bluetooth/shell/audio.conf index 40d24233f6a84df..a040ba44d8ce970 100644 --- a/tests/bluetooth/shell/audio.conf +++ b/tests/bluetooth/shell/audio.conf @@ -29,6 +29,10 @@ CONFIG_BT_MAX_CONN=3 CONFIG_BT_MAX_PAIRED=3 CONFIG_BT_KEYS_OVERWRITE_OLDEST=y +CONFIG_BT_L2CAP_TX_MTU=128 +CONFIG_BT_BUF_ACL_RX_SIZE=255 +CONFIG_BT_BUF_ACL_TX_SIZE=251 + CONFIG_BT_SETTINGS=y CONFIG_FLASH=y CONFIG_FLASH_MAP=y @@ -60,7 +64,6 @@ CONFIG_BT_BAP_UNICAST_CLIENT=y CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT=4 CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT=2 CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT=2 - CONFIG_BT_CODEC_MAX_METADATA_COUNT=10 CONFIG_BT_CODEC_MAX_DATA_LEN=40 CONFIG_BT_CODEC_MAX_DATA_COUNT=10 @@ -149,6 +152,26 @@ CONFIG_BT_CAP_ACCEPTOR=y CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER=y CONFIG_BT_CAP_INITIATOR=y +# Gaming Profile +CONFIG_BT_GMAP_UGG=y +CONFIG_BT_GMAP_UGG_MULTIPLEX_SUPPORT=y +CONFIG_BT_GMAP_UGG_96KBPS_SUPPORT=y +CONFIG_BT_GMAP_UGG_MULTISINK_SUPPORT=y +CONFIG_BT_GMAP_UGT=y +CONFIG_BT_GMAP_UGT_SOURCE_SUPPORT=y +CONFIG_BT_GMAP_UGT_80KBPS_SOURCE_SUPPORT=y +CONFIG_BT_GMAP_UGT_SINK_SUPPORT=y +CONFIG_BT_GMAP_UGT_64KBPS_SINK_SUPPORT=y +CONFIG_BT_GMAP_UGT_MULTIPLEX_SUPPORT=y +CONFIG_BT_GMAP_UGT_MULTISINK_SUPPORT=y +CONFIG_BT_GMAP_UGT_MULTISOURCE_SUPPORT=y +CONFIG_BT_GMAP_BGS=y +CONFIG_BT_GMAP_BGS_SERVICE=y +CONFIG_BT_GMAP_BGS_96KBPS_SUPPORT=y +CONFIG_BT_GMAP_BGR=y +CONFIG_BT_GMAP_BGR_MULTISINK_SUPPORT=y +CONFIG_BT_GMAP_BGR_MULTIPLEX_SUPPORT=y + # DEBUGGING CONFIG_LOG=y CONFIG_BT_AUDIO_LOG_LEVEL_DBG=y @@ -183,3 +206,6 @@ CONFIG_BT_BAP_ISO_LOG_LEVEL_DBG=y CONFIG_BT_AUDIO_CODEC_LOG_LEVEL_DBG=y CONFIG_BT_CSIP_SET_COORDINATOR_LOG_LEVEL_DBG=y CONFIG_BT_CSIP_SET_MEMBER_LOG_LEVEL_DBG=y +CONFIG_BT_GMAP_LOG_LEVEL_DBG=y +CONFIG_BT_CAP_ACCEPTOR_LOG_LEVEL_DBG=y +CONFIG_BT_CAP_INITIATOR_LOG_LEVEL_DBG=y diff --git a/tests/bsim/bluetooth/audio/prj.conf b/tests/bsim/bluetooth/audio/prj.conf index 4f9459c168e0b5d..209626f590aebfc 100644 --- a/tests/bsim/bluetooth/audio/prj.conf +++ b/tests/bsim/bluetooth/audio/prj.conf @@ -13,11 +13,14 @@ CONFIG_BT_MAX_CONN=5 CONFIG_BT_MAX_PAIRED=5 CONFIG_BT_GATT_DYNAMIC_DB=y CONFIG_BT_SMP=y +CONFIG_BT_L2CAP_TX_MTU=128 +CONFIG_BT_BUF_ACL_RX_SIZE=255 +CONFIG_BT_BUF_ACL_TX_SIZE=251 CONFIG_BT_AUDIO=y CONFIG_BT_BAP_UNICAST_SERVER=y CONFIG_BT_BAP_UNICAST_CLIENT=y -CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT=2 +CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT=4 CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT=2 CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT=2 CONFIG_BT_ASCS_ASE_SNK_COUNT=2 @@ -30,7 +33,7 @@ CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT=1 CONFIG_BT_BAP_BROADCAST_SNK_SUBGROUP_COUNT=1 CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT=1 CONFIG_BT_ISO_TX_BUF_COUNT=4 -CONFIG_BT_ISO_MAX_CHAN=2 +CONFIG_BT_ISO_MAX_CHAN=4 # Needed for Periodic Advertising Sync Transfer CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER=y @@ -118,6 +121,20 @@ CONFIG_BT_CAP_INITIATOR=y # Telephony and Media Audio Profile CONFIG_BT_TMAP=y +# Gaming Audio Profile +CONFIG_BT_GMAP_UGG=y +CONFIG_BT_GMAP_UGG_MULTIPLEX_SUPPORT=y +CONFIG_BT_GMAP_UGG_96KBPS_SUPPORT=y +CONFIG_BT_GMAP_UGG_MULTISINK_SUPPORT=y +CONFIG_BT_GMAP_UGT=y +CONFIG_BT_GMAP_UGT_SOURCE_SUPPORT=y +CONFIG_BT_GMAP_UGT_80KBPS_SOURCE_SUPPORT=y +CONFIG_BT_GMAP_UGT_SINK_SUPPORT=y +CONFIG_BT_GMAP_UGT_64KBPS_SINK_SUPPORT=y +CONFIG_BT_GMAP_UGT_MULTIPLEX_SUPPORT=y +CONFIG_BT_GMAP_UGT_MULTISINK_SUPPORT=y +CONFIG_BT_GMAP_UGT_MULTISOURCE_SUPPORT=y + # DEBUGGING CONFIG_LOG=y CONFIG_LOG_FUNC_NAME_PREFIX_ERR=y @@ -160,18 +177,34 @@ CONFIG_BT_CAP_INITIATOR_LOG_LEVEL_DBG=y # LOGGING CONFIG_LOG_MODE_IMMEDIATE=y -# Controller Broadcast ISO configs +# Controller Settings +CONFIG_BT_LL_SW_SPLIT=y +CONFIG_BT_CTLR_PHY_CODED=y +CONFIG_BT_CTLR_ADVANCED_FEATURES=y +CONFIG_BT_CTLR_ADV_AUX_PDU_BACK2BACK=y +CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK=y +CONFIG_BT_CTLR_ADV_DATA_BUF_MAX=6 +CONFIG_BT_CTLR_ADV_RESERVE_MAX=n +CONFIG_BT_CTLR_CENTRAL_RESERVE_MAX=n +CONFIG_BT_CTLR_SCAN_UNRESERVED=y CONFIG_BT_CTLR_ADV_ISO=y -CONFIG_BT_CTLR_SYNC_ISO=y CONFIG_BT_CTLR_SCAN_DATA_LEN_MAX=255 +CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=191 # Supports the highest SDU size required by any BAP LC3 presets (155) CONFIG_BT_CTLR_ISO_TX_BUFFER_SIZE=155 -CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=191 +CONFIG_BT_CTLR_ISO_TX_BUFFERS=8 +CONFIG_BT_CTLR_ISO_RX_BUFFERS=1 + +# Controller ISO Broadcast Settings +CONFIG_BT_CTLR_SYNC_ISO=y CONFIG_BT_CTLR_ADV_ISO_STREAM_COUNT=1 CONFIG_BT_CTLR_SYNC_ISO_STREAM_MAX=1 -# Controller Connected ISO configs +# Controller ISO Unicast Settings CONFIG_BT_CTLR_CENTRAL_ISO=y CONFIG_BT_CTLR_PERIPHERAL_ISO=y -CONFIG_BT_CTLR_ISO_TX_BUFFERS=3 +CONFIG_BT_CTLR_CONN_ISO_STREAMS=2 +CONFIG_BT_CTLR_CONN_ISO_STREAMS_PER_GROUP=2 +CONFIG_BT_CTLR_ISOAL_SOURCES=2 +CONFIG_BT_CTLR_ISOAL_SINKS=2 diff --git a/tests/bsim/bluetooth/audio/src/bap_common.h b/tests/bsim/bluetooth/audio/src/bap_common.h new file mode 100644 index 000000000000000..acf377ebdaeb208 --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/bap_common.h @@ -0,0 +1,62 @@ +/** + * Common functions and helpers for audio BSIM audio tests + * + * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_TEST_BSIM_BT_AUDIO_TEST_COMMON_ +#define ZEPHYR_TEST_BSIM_BT_AUDIO_TEST_COMMON_ + +#include +#include + +void print_hex(const uint8_t *ptr, size_t len); +void print_codec(const struct bt_codec *codec); +void print_qos(const struct bt_codec_qos *qos); + +static inline bool valid_metadata_type(uint8_t type, uint8_t len) +{ + switch (type) { + case BT_AUDIO_METADATA_TYPE_PREF_CONTEXT: + case BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT: + if (len != 2) { + return false; + } + + return true; + case BT_AUDIO_METADATA_TYPE_STREAM_LANG: + if (len != 3) { + return false; + } + + return true; + case BT_AUDIO_METADATA_TYPE_PARENTAL_RATING: + if (len != 1) { + return false; + } + + return true; + case BT_AUDIO_METADATA_TYPE_EXTENDED: /* 1 - 255 octets */ + case BT_AUDIO_METADATA_TYPE_VENDOR: /* 1 - 255 octets */ + if (len < 1) { + return false; + } + + return true; + case BT_AUDIO_METADATA_TYPE_CCID_LIST: /* 2 - 254 octets */ + if (len < 2) { + return false; + } + + return true; + case BT_AUDIO_METADATA_TYPE_PROGRAM_INFO: /* 0 - 255 octets */ + case BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: /* 0 - 255 octets */ + return true; + default: + return false; + } +} + +#endif /* ZEPHYR_TEST_BSIM_BT_AUDIO_TEST_COMMON_ */ diff --git a/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c b/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c index 275379087e199de..16b165a7845e545 100644 --- a/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_unicast_client_test.c @@ -12,7 +12,7 @@ #include #include #include "common.h" -#include "bap_unicast_common.h" +#include "bap_common.h" #define BAP_STREAM_RETRY_WAIT K_MSEC(100) diff --git a/tests/bsim/bluetooth/audio/src/bap_unicast_common.c b/tests/bsim/bluetooth/audio/src/bap_unicast_common.c index c335a1929fb4b36..5ac25434dd4d5c3 100644 --- a/tests/bsim/bluetooth/audio/src/bap_unicast_common.c +++ b/tests/bsim/bluetooth/audio/src/bap_unicast_common.c @@ -5,7 +5,7 @@ */ #include "common.h" -#include "bap_unicast_common.h" +#include "bap_common.h" void print_hex(const uint8_t *ptr, size_t len) { diff --git a/tests/bsim/bluetooth/audio/src/bap_unicast_common.h b/tests/bsim/bluetooth/audio/src/bap_unicast_common.h deleted file mode 100644 index acc8ccd963458b5..000000000000000 --- a/tests/bsim/bluetooth/audio/src/bap_unicast_common.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Common functions and helpers for unicast audio BSIM audio tests - * - * Copyright (c) 2021-2023 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef ZEPHYR_TEST_BSIM_BT_AUDIO_TEST_UNICAST_COMMON_ -#define ZEPHYR_TEST_BSIM_BT_AUDIO_TEST_UNICAST_COMMON_ - -#include -#include - -void print_hex(const uint8_t *ptr, size_t len); -void print_codec(const struct bt_codec *codec); -void print_qos(const struct bt_codec_qos *qos); - -#endif /* ZEPHYR_TEST_BSIM_BT_AUDIO_TEST_UNICAST_COMMON_ */ diff --git a/tests/bsim/bluetooth/audio/src/bap_unicast_server_test.c b/tests/bsim/bluetooth/audio/src/bap_unicast_server_test.c index 88ab2fd7164e026..320467e30c92b8d 100644 --- a/tests/bsim/bluetooth/audio/src/bap_unicast_server_test.c +++ b/tests/bsim/bluetooth/audio/src/bap_unicast_server_test.c @@ -11,7 +11,7 @@ #include #include #include "common.h" -#include "bap_unicast_common.h" +#include "bap_common.h" extern enum bst_result_t bst_result; @@ -160,49 +160,6 @@ static int lc3_start(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) return 0; } -static bool valid_metadata_type(uint8_t type, uint8_t len) -{ - switch (type) { - case BT_AUDIO_METADATA_TYPE_PREF_CONTEXT: - case BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT: - if (len != 2) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_STREAM_LANG: - if (len != 3) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_PARENTAL_RATING: - if (len != 1) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_EXTENDED: /* 1 - 255 octets */ - case BT_AUDIO_METADATA_TYPE_VENDOR: /* 1 - 255 octets */ - if (len < 1) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_CCID_LIST: /* 2 - 254 octets */ - if (len < 2) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_PROGRAM_INFO: /* 0 - 255 octets */ - case BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: /* 0 - 255 octets */ - return true; - default: - return false; - } -} - static int lc3_metadata(struct bt_bap_stream *stream, const struct bt_codec_data *meta, size_t meta_count, struct bt_bap_ascs_rsp *rsp) { diff --git a/tests/bsim/bluetooth/audio/src/cap_acceptor_test.c b/tests/bsim/bluetooth/audio/src/cap_acceptor_test.c index eae27963e580117..dde6d9472320409 100644 --- a/tests/bsim/bluetooth/audio/src/cap_acceptor_test.c +++ b/tests/bsim/bluetooth/audio/src/cap_acceptor_test.c @@ -11,7 +11,7 @@ #include #include #include "common.h" -#include "bap_unicast_common.h" +#include "bap_common.h" extern enum bst_result_t bst_result; @@ -325,49 +325,6 @@ static int unicast_server_start(struct bt_bap_stream *stream, struct bt_bap_ascs return 0; } -static bool valid_metadata_type(uint8_t type, uint8_t len) -{ - switch (type) { - case BT_AUDIO_METADATA_TYPE_PREF_CONTEXT: - case BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT: - if (len != 2) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_STREAM_LANG: - if (len != 3) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_PARENTAL_RATING: - if (len != 1) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_EXTENDED: /* 1 - 255 octets */ - case BT_AUDIO_METADATA_TYPE_VENDOR: /* 1 - 255 octets */ - if (len < 1) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_CCID_LIST: /* 2 - 254 octets */ - if (len < 2) { - return false; - } - - return true; - case BT_AUDIO_METADATA_TYPE_PROGRAM_INFO: /* 0 - 255 octets */ - case BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI: /* 0 - 255 octets */ - return true; - default: - return false; - } -} - static int unicast_server_metadata(struct bt_bap_stream *stream, const struct bt_codec_data *meta, size_t meta_count, struct bt_bap_ascs_rsp *rsp) { diff --git a/tests/bsim/bluetooth/audio/src/cap_initiator_unicast_test.c b/tests/bsim/bluetooth/audio/src/cap_initiator_unicast_test.c index 39ae4d672b22baa..cc0ac9cdd2ff864 100644 --- a/tests/bsim/bluetooth/audio/src/cap_initiator_unicast_test.c +++ b/tests/bsim/bluetooth/audio/src/cap_initiator_unicast_test.c @@ -12,7 +12,7 @@ #include #include #include "common.h" -#include "bap_unicast_common.h" +#include "bap_common.h" extern enum bst_result_t bst_result; diff --git a/tests/bsim/bluetooth/audio/src/common.c b/tests/bsim/bluetooth/audio/src/common.c index 3bac03064caa3a0..6c6a9c31f491ccd 100644 --- a/tests/bsim/bluetooth/audio/src/common.c +++ b/tests/bsim/bluetooth/audio/src/common.c @@ -10,6 +10,7 @@ extern enum bst_result_t bst_result; struct bt_conn *default_conn; atomic_t flag_connected; +atomic_t flag_disconnected; atomic_t flag_conn_updated; const struct bt_data ad[AD_SIZE] = { @@ -91,6 +92,7 @@ void disconnected(struct bt_conn *conn, uint8_t reason) default_conn = NULL; UNSET_FLAG(flag_connected); UNSET_FLAG(flag_conn_updated); + SET_FLAG(flag_disconnected); } static void conn_param_updated_cb(struct bt_conn *conn, uint16_t interval, uint16_t latency, diff --git a/tests/bsim/bluetooth/audio/src/common.h b/tests/bsim/bluetooth/audio/src/common.h index ddccdb440255c5d..96138124459454a 100644 --- a/tests/bsim/bluetooth/audio/src/common.h +++ b/tests/bsim/bluetooth/audio/src/common.h @@ -58,6 +58,7 @@ extern const struct bt_data ad[AD_SIZE]; extern struct bt_conn *default_conn; extern atomic_t flag_connected; +extern atomic_t flag_disconnected; extern atomic_t flag_conn_updated; void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, diff --git a/tests/bsim/bluetooth/audio/src/gmap_ugg_test.c b/tests/bsim/bluetooth/audio/src/gmap_ugg_test.c new file mode 100644 index 000000000000000..dce053d819f182d --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/gmap_ugg_test.c @@ -0,0 +1,1223 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_BT_GMAP_UGG) + +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "bap_common.h" + +#define UNICAST_SINK_SUPPORTED (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0) +#define UNICAST_SRC_SUPPORTED (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0) + +#define CONTEXT (BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | BT_AUDIO_CONTEXT_TYPE_GAME) +#define LOCATION (BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT) + +#define GMAP_AC_MAX_CONN 2U +#define GMAP_AC_MAX_SNK (2U * GMAP_AC_MAX_CONN) +#define GMAP_AC_MAX_SRC (2U * GMAP_AC_MAX_CONN) +#define GMAP_AC_MAX_PAIR MAX(GMAP_AC_MAX_SNK, GMAP_AC_MAX_SRC) +#define GMAP_AC_MAX_STREAM (GMAP_AC_MAX_SNK + GMAP_AC_MAX_SRC) + +#define MAX_CIS_COUNT 2U +#define ISO_ENQUEUE_COUNT 2U +#define TOTAL_BUF_NEEDED (ISO_ENQUEUE_COUNT * MAX_CIS_COUNT) + +BUILD_ASSERT(CONFIG_BT_ISO_TX_BUF_COUNT >= TOTAL_BUF_NEEDED, + "CONFIG_BT_ISO_TX_BUF_COUNT should be at least ISO_ENQUEUE_COUNT * MAX_CIS_COUNT"); + +NET_BUF_POOL_FIXED_DEFINE(tx_pool, TOTAL_BUF_NEEDED, BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +extern enum bst_result_t bst_result; +const struct named_lc3_preset *snk_named_preset; +const struct named_lc3_preset *src_named_preset; + +struct unicast_stream { + struct bt_cap_stream stream; + struct bt_codec codec; + struct bt_codec_qos qos; +}; + +struct named_lc3_preset { + const char *name; + struct bt_bap_lc3_preset preset; +}; + +struct gmap_ac_param { + char *name; + size_t conn_cnt; + size_t snk_cnt[GMAP_AC_MAX_CONN]; + size_t src_cnt[GMAP_AC_MAX_CONN]; + size_t snk_chan_cnt; + const struct named_lc3_preset *snk_named_preset; + const struct named_lc3_preset *src_named_preset; +}; + +static struct named_lc3_preset gmap_unicast_snk_presets[] = { + {"32_1_gr", BT_GMAP_LC3_PRESET_32_1_GR(LOCATION, CONTEXT)}, + {"32_2_gr", BT_GMAP_LC3_PRESET_32_2_GR(LOCATION, CONTEXT)}, + {"48_1_gr", BT_GMAP_LC3_PRESET_48_1_GR(LOCATION, CONTEXT)}, + {"48_2_gr", BT_GMAP_LC3_PRESET_48_2_GR(LOCATION, CONTEXT)}, + {"48_3_gr", BT_GMAP_LC3_PRESET_48_3_GR(LOCATION, CONTEXT)}, + {"48_4_gr", BT_GMAP_LC3_PRESET_48_4_GR(LOCATION, CONTEXT)}, +}; + +static struct named_lc3_preset gmap_unicast_src_presets[] = { + {"32_1_gs", BT_GMAP_LC3_PRESET_32_1_GS(LOCATION, CONTEXT)}, + {"32_2_gs", BT_GMAP_LC3_PRESET_32_2_GS(LOCATION, CONTEXT)}, + {"48_1_gs", BT_GMAP_LC3_PRESET_48_1_GS(LOCATION, CONTEXT)}, + {"48_2_gs", BT_GMAP_LC3_PRESET_48_2_GS(LOCATION, CONTEXT)}, +}; + +static struct named_lc3_preset gmap_broadcast_presets[] = { + {"48_1_g", BT_GMAP_LC3_PRESET_48_1_G(LOCATION, CONTEXT)}, + {"48_2_g", BT_GMAP_LC3_PRESET_48_2_G(LOCATION, CONTEXT)}, + {"48_3_g", BT_GMAP_LC3_PRESET_48_3_G(LOCATION, CONTEXT)}, + {"48_4_g", BT_GMAP_LC3_PRESET_48_4_G(LOCATION, CONTEXT)}, +}; + +struct named_lc3_preset named_preset; + +static struct unicast_stream unicast_streams[GMAP_AC_MAX_STREAM]; +static struct bt_bap_ep *sink_eps[GMAP_AC_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; +static struct bt_bap_ep *source_eps[GMAP_AC_MAX_CONN][CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; +static struct bt_conn *connected_conns[GMAP_AC_MAX_CONN]; +static size_t connected_conn_cnt; + +static K_SEM_DEFINE(sem_stream_started, 0U, ARRAY_SIZE(unicast_streams)); +static K_SEM_DEFINE(sem_stream_stopped, 0U, ARRAY_SIZE(unicast_streams)); + +CREATE_FLAG(flag_discovered); +CREATE_FLAG(flag_started); +CREATE_FLAG(flag_updated); +CREATE_FLAG(flag_stopped); +CREATE_FLAG(flag_mtu_exchanged); +CREATE_FLAG(flag_sink_discovered); +CREATE_FLAG(flag_source_discovered); +CREATE_FLAG(flag_stream_stopping); + +const struct named_lc3_preset *gmap_get_named_preset(bool is_unicast, enum bt_audio_dir dir, + const char *preset_arg) +{ + if (is_unicast) { + if (dir == BT_AUDIO_DIR_SINK) { + for (size_t i = 0U; i < ARRAY_SIZE(gmap_unicast_snk_presets); i++) { + if (!strcmp(preset_arg, gmap_unicast_snk_presets[i].name)) { + return &gmap_unicast_snk_presets[i]; + } + } + } else if (dir == BT_AUDIO_DIR_SOURCE) { + for (size_t i = 0U; i < ARRAY_SIZE(gmap_unicast_src_presets); i++) { + if (!strcmp(preset_arg, gmap_unicast_src_presets[i].name)) { + return &gmap_unicast_src_presets[i]; + } + } + } + } else { + + for (size_t i = 0U; i < ARRAY_SIZE(gmap_broadcast_presets); i++) { + if (!strcmp(preset_arg, gmap_broadcast_presets[i].name)) { + return &gmap_broadcast_presets[i]; + } + } + } + + return NULL; +} + +static inline void copy_unicast_stream_preset(struct unicast_stream *stream, + const struct named_lc3_preset *named_preset) +{ + memcpy(&stream->qos, &named_preset->preset.qos, sizeof(stream->qos)); + memcpy(&stream->codec, &named_preset->preset.codec, sizeof(stream->codec)); + +#if CONFIG_BT_CODEC_MAX_DATA_COUNT > 0 && CONFIG_BT_CODEC_MAX_DATA_LEN > 0 + /* Need to update the `bt_data.data` pointer to the new value after copying the codec */ + for (size_t i = 0U; i < ARRAY_SIZE(stream->codec.data); i++) { + const struct bt_codec_data *preset_data = &named_preset->preset.codec.data[i]; + struct bt_codec_data *data = &stream->codec.data[i]; + const uint8_t data_len = preset_data->data.data_len; + + data->data.data = data->value; + data->data.data_len = data_len; + memcpy(data->value, preset_data->data.data, data_len); + } +#endif /* CONFIG_BT_CODEC_MAX_METADATA_COUNT > 0 && CONFIG_BT_CODEC_MAX_DATA_LEN > 0 */ + +#if CONFIG_BT_CODEC_MAX_METADATA_COUNT > 0 && CONFIG_BT_CODEC_MAX_DATA_LEN > 0 + for (size_t i = 0U; i < ARRAY_SIZE(stream->codec.meta); i++) { + const struct bt_codec_data *preset_data = &named_preset->preset.codec.meta[i]; + struct bt_codec_data *data = &stream->codec.meta[i]; + const uint8_t data_len = preset_data->data.data_len; + + data->data.data = data->value; + data->data.data_len = data_len; + memcpy(data->value, preset_data->data.data, data_len); + } +#endif /* CONFIG_BT_CODEC_MAX_METADATA_COUNT > 0 && CONFIG_BT_CODEC_MAX_DATA_LEN > 0 */ +} + +static void stream_sent_cb(struct bt_bap_stream *stream) +{ + static uint8_t mock_data[CONFIG_BT_ISO_TX_MTU]; + static bool mock_data_initialized; + static uint32_t seq_num; + struct net_buf *buf; + int ret; + + if (named_preset.preset.qos.sdu > CONFIG_BT_ISO_TX_MTU) { + FAIL("Invalid SDU %u for the MTU: %d\n", named_preset.preset.qos.sdu, + CONFIG_BT_ISO_TX_MTU); + return; + } + + if (TEST_FLAG(flag_stream_stopping)) { + return; + } + + if (!mock_data_initialized) { + for (size_t i = 0U; i < ARRAY_SIZE(mock_data); i++) { + /* Initialize mock data */ + mock_data[i] = (uint8_t)i; + } + mock_data_initialized = true; + } + + buf = net_buf_alloc(&tx_pool, K_FOREVER); + if (buf == NULL) { + printk("Could not allocate buffer when sending on %p\n", stream); + return; + } + + net_buf_reserve(buf, BT_ISO_CHAN_SEND_RESERVE); + net_buf_add_mem(buf, mock_data, named_preset.preset.qos.sdu); + ret = bt_bap_stream_send(stream, buf, seq_num++, BT_ISO_TIMESTAMP_NONE); + if (ret < 0) { + /* This will end send on this stream. */ + printk("Unable to send data on %p: %d\n", stream, ret); + net_buf_unref(buf); + return; + } +} + +static void stream_configured_cb(struct bt_bap_stream *stream, const struct bt_codec_qos_pref *pref) +{ + printk("Configured stream %p\n", stream); + + /* TODO: The preference should be used/taken into account when + * setting the QoS + */ +} + +static void stream_qos_set_cb(struct bt_bap_stream *stream) +{ + printk("QoS set stream %p\n", stream); +} + +static void stream_enabled_cb(struct bt_bap_stream *stream) +{ + printk("Enabled stream %p\n", stream); +} + +static void stream_started_cb(struct bt_bap_stream *stream) +{ + printk("Started stream %p\n", stream); + k_sem_give(&sem_stream_started); +} + +static void stream_metadata_updated_cb(struct bt_bap_stream *stream) +{ + printk("Metadata updated stream %p\n", stream); +} + +static void stream_disabled_cb(struct bt_bap_stream *stream) +{ + printk("Disabled stream %p\n", stream); +} + +static void stream_stopped_cb(struct bt_bap_stream *stream, uint8_t reason) +{ + printk("Stream %p stopped with reason 0x%02X\n", stream, reason); + k_sem_give(&sem_stream_stopped); +} + +static void stream_released_cb(struct bt_bap_stream *stream) +{ + printk("Released stream %p\n", stream); +} + +static struct bt_bap_stream_ops stream_ops = { + .configured = stream_configured_cb, + .qos_set = stream_qos_set_cb, + .enabled = stream_enabled_cb, + .started = stream_started_cb, + .metadata_updated = stream_metadata_updated_cb, + .disabled = stream_disabled_cb, + .stopped = stream_stopped_cb, + .released = stream_released_cb, + .sent = stream_sent_cb, +}; + +static void cap_discovery_complete_cb(struct bt_conn *conn, int err, + const struct bt_csip_set_coordinator_csis_inst *csis_inst) +{ + if (err != 0) { + FAIL("Failed to discover CAS: %d\n", err); + + return; + } + + if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER)) { + if (csis_inst == NULL) { + FAIL("Failed to discover CAS CSIS\n"); + + return; + } + + printk("Found CAS with CSIS %p\n", csis_inst); + } else { + printk("Found CAS\n"); + } + + SET_FLAG(flag_discovered); +} + +static void unicast_start_complete_cb(struct bt_bap_unicast_group *unicast_group, int err, + struct bt_conn *conn) +{ + if (err != 0) { + FAIL("Failed to start (failing conn %p): %d\n", conn, err); + + return; + } + + SET_FLAG(flag_started); +} + +static void unicast_update_complete_cb(int err, struct bt_conn *conn) +{ + if (err != 0) { + FAIL("Failed to update (failing conn %p): %d\n", conn, err); + + return; + } + + SET_FLAG(flag_updated); +} + +static void unicast_stop_complete_cb(struct bt_bap_unicast_group *unicast_group, int err, + struct bt_conn *conn) +{ + if (err != 0) { + FAIL("Failed to stop (failing conn %p): %d\n", conn, err); + + return; + } + + SET_FLAG(flag_stopped); +} + +static struct bt_cap_initiator_cb cap_cb = { + .unicast_discovery_complete = cap_discovery_complete_cb, + .unicast_start_complete = unicast_start_complete_cb, + .unicast_update_complete = unicast_update_complete_cb, + .unicast_stop_complete = unicast_stop_complete_cb, +}; + +static void add_remote_sink_ep(struct bt_conn *conn, struct bt_bap_ep *ep) +{ + for (size_t i = 0U; i < ARRAY_SIZE(sink_eps[bt_conn_index(conn)]); i++) { + if (sink_eps[bt_conn_index(conn)][i] == NULL) { + printk("Conn %p: Sink #%zu: ep %p\n", conn, i, ep); + sink_eps[bt_conn_index(conn)][i] = ep; + break; + } + } +} + +static void add_remote_source_ep(struct bt_conn *conn, struct bt_bap_ep *ep) +{ + for (size_t i = 0U; i < ARRAY_SIZE(source_eps[bt_conn_index(conn)]); i++) { + if (source_eps[bt_conn_index(conn)][i] == NULL) { + printk("Conn %p: Source #%zu: ep %p\n", conn, i, ep); + source_eps[bt_conn_index(conn)][i] = ep; + break; + } + } +} + +static void bap_pac_record_cb(struct bt_conn *conn, enum bt_audio_dir dir, + const struct bt_codec *codec) +{ + printk("conn %p codec %p dir 0x%02x\n", conn, codec, dir); + + print_codec(codec); +} + +static void bap_endpoint_cb(struct bt_conn *conn, enum bt_audio_dir dir, struct bt_bap_ep *ep) +{ + if (dir == BT_AUDIO_DIR_SINK) { + add_remote_sink_ep(conn, ep); + } else if (dir == BT_AUDIO_DIR_SOURCE) { + add_remote_source_ep(conn, ep); + } +} + +static void bap_discover_cb(struct bt_conn *conn, int err, enum bt_audio_dir dir) +{ + if (err != 0) { + FAIL("Discovery failed for dir %u: %d\n", dir, err); + return; + } + + if (dir == BT_AUDIO_DIR_SINK) { + printk("Sink discover complete\n"); + SET_FLAG(flag_sink_discovered); + } else if (dir == BT_AUDIO_DIR_SOURCE) { + printk("Source discover complete\n"); + SET_FLAG(flag_source_discovered); + } +} + +static struct bt_bap_unicast_client_cb unicast_client_cbs = { + .pac_record = bap_pac_record_cb, + .endpoint = bap_endpoint_cb, + .discover = bap_discover_cb, +}; + +static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx) +{ + printk("MTU exchanged\n"); + SET_FLAG(flag_mtu_exchanged); +} + +static struct bt_gatt_cb gatt_callbacks = { + .att_mtu_updated = att_mtu_updated, +}; + +static void init(void) +{ + int err; + + err = bt_enable(NULL); + if (err != 0) { + FAIL("Bluetooth enable failed (err %d)\n", err); + return; + } + + bt_gatt_cb_register(&gatt_callbacks); + + err = bt_bap_unicast_client_register_cb(&unicast_client_cbs); + if (err != 0) { + FAIL("Failed to register CAP callbacks (err %d)\n", err); + return; + } + + err = bt_cap_initiator_register_cb(&cap_cb); + if (err != 0) { + FAIL("Failed to register CAP callbacks (err %d)\n", err); + return; + } + + for (size_t i = 0; i < ARRAY_SIZE(unicast_streams); i++) { + bt_cap_stream_ops_register(&unicast_streams[i].stream, &stream_ops); + } +} + +static void gmap_device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type, + struct net_buf_simple *ad) +{ + char addr_str[BT_ADDR_LE_STR_LEN]; + struct bt_conn *conn; + int err; + + /* We're only interested in connectable events */ + if (type != BT_HCI_ADV_IND && type != BT_HCI_ADV_DIRECT_IND) { + return; + } + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr); + if (conn != NULL) { + /* Already connected to this device */ + bt_conn_unref(conn); + return; + } + + bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); + printk("Device found: %s (RSSI %d)\n", addr_str, rssi); + + /* connect only to devices in close proximity */ + if (rssi < -70) { + FAIL("RSSI too low"); + return; + } + + printk("Stopping scan\n"); + if (bt_le_scan_stop()) { + FAIL("Could not stop scan"); + return; + } + + err = bt_conn_le_create( + addr, BT_CONN_LE_CREATE_CONN, + BT_LE_CONN_PARAM(BT_GAP_INIT_CONN_INT_MIN, BT_GAP_INIT_CONN_INT_MIN, 0, 400), + &connected_conns[connected_conn_cnt]); + if (err) { + FAIL("Could not connect to peer: %d", err); + } +} + +static void scan_and_connect(void) +{ + int err; + + UNSET_FLAG(flag_connected); + + err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, gmap_device_found); + if (err != 0) { + FAIL("Scanning failed to start (err %d)\n", err); + return; + } + + printk("Scanning successfully started\n"); + WAIT_FOR_FLAG(flag_connected); + connected_conn_cnt++; +} + +static void discover_sink(struct bt_conn *conn) +{ + int err; + + UNSET_FLAG(flag_sink_discovered); + + err = bt_bap_unicast_client_discover(conn, BT_AUDIO_DIR_SINK); + if (err != 0) { + printk("Failed to discover sink: %d\n", err); + return; + } + + WAIT_FOR_FLAG(flag_sink_discovered); +} + +static void discover_source(struct bt_conn *conn) +{ + int err; + + UNSET_FLAG(flag_source_discovered); + + err = bt_bap_unicast_client_discover(conn, BT_AUDIO_DIR_SOURCE); + if (err != 0) { + printk("Failed to discover source: %d\n", err); + return; + } + + WAIT_FOR_FLAG(flag_source_discovered); +} + +static void discover_cas(struct bt_conn *conn) +{ + int err; + + UNSET_FLAG(flag_discovered); + + err = bt_cap_initiator_unicast_discover(conn); + if (err != 0) { + printk("Failed to discover CAS: %d\n", err); + return; + } + + WAIT_FOR_FLAG(flag_discovered); +} + +static int gmap_ac_create_unicast_group(const struct gmap_ac_param *param, + struct unicast_stream *snk_uni_streams[], size_t snk_cnt, + struct unicast_stream *src_uni_streams[], size_t src_cnt, + struct bt_bap_unicast_group **unicast_group) +{ + struct bt_bap_unicast_group_stream_param snk_group_stream_params[GMAP_AC_MAX_SNK] = {0}; + struct bt_bap_unicast_group_stream_param src_group_stream_params[GMAP_AC_MAX_SRC] = {0}; + struct bt_bap_unicast_group_stream_pair_param pair_params[GMAP_AC_MAX_PAIR] = {0}; + struct bt_bap_unicast_group_param group_param = {0}; + struct bt_codec_qos *snk_qos[GMAP_AC_MAX_SNK]; + struct bt_codec_qos *src_qos[GMAP_AC_MAX_SRC]; + size_t snk_stream_cnt = 0U; + size_t src_stream_cnt = 0U; + size_t pair_cnt = 0U; + + for (size_t i = 0U; i < snk_cnt; i++) { + snk_qos[i] = &snk_uni_streams[i]->qos; + } + + for (size_t i = 0U; i < src_cnt; i++) { + src_qos[i] = &src_uni_streams[i]->qos; + } + + /* Create Group + * + * First setup the individual stream parameters and then match them in pairs by connection + * and direction + */ + for (size_t i = 0U; i < snk_cnt; i++) { + snk_group_stream_params[i].qos = snk_qos[i]; + snk_group_stream_params[i].stream = &snk_uni_streams[i]->stream.bap_stream; + } + for (size_t i = 0U; i < src_cnt; i++) { + src_group_stream_params[i].qos = src_qos[i]; + src_group_stream_params[i].stream = &src_uni_streams[i]->stream.bap_stream; + } + + for (size_t i = 0U; i < param->conn_cnt; i++) { + for (size_t j = 0; j < MAX(param->snk_cnt[i], param->src_cnt[i]); j++) { + if (param->snk_cnt[i] > j) { + pair_params[pair_cnt].tx_param = + &snk_group_stream_params[snk_stream_cnt++]; + } else { + pair_params[pair_cnt].tx_param = NULL; + } + + if (param->src_cnt[i] > j) { + pair_params[pair_cnt].rx_param = + &src_group_stream_params[src_stream_cnt++]; + } else { + pair_params[pair_cnt].rx_param = NULL; + } + + pair_cnt++; + } + } + + group_param.packing = BT_ISO_PACKING_SEQUENTIAL; + group_param.params = pair_params; + group_param.params_count = pair_cnt; + + return bt_bap_unicast_group_create(&group_param, unicast_group); +} + +static int set_chan_alloc(enum bt_audio_location loc, struct bt_codec *codec) +{ + for (size_t i = 0U; i < codec->data_count; i++) { + struct bt_codec_data *data = &codec->data[i]; + + /* Overwrite the location value */ + if (data->data.type == BT_CODEC_CONFIG_LC3_CHAN_ALLOC) { + const uint32_t loc_32 = loc; + + sys_put_le32(loc_32, data->value); + + break; + } + } + + return 0; +} + +static int gmap_ac_cap_unicast_start(const struct gmap_ac_param *param, + struct unicast_stream *snk_uni_streams[], size_t snk_cnt, + struct unicast_stream *src_uni_streams[], size_t src_cnt, + struct bt_bap_unicast_group *unicast_group) +{ + struct bt_cap_unicast_audio_start_stream_param stream_params[GMAP_AC_MAX_STREAM] = {0}; + struct bt_cap_unicast_audio_start_param start_param = {0}; + struct bt_cap_stream *snk_cap_streams[GMAP_AC_MAX_SNK] = {0}; + struct bt_cap_stream *src_cap_streams[GMAP_AC_MAX_SRC] = {0}; + struct bt_codec_qos *snk_qos[GMAP_AC_MAX_SNK] = {0}; + struct bt_codec_qos *src_qos[GMAP_AC_MAX_SRC] = {0}; + struct bt_codec *snk_codecs[GMAP_AC_MAX_SNK] = {0}; + struct bt_codec *src_codecs[GMAP_AC_MAX_SRC] = {0}; + struct bt_bap_ep *snk_eps[GMAP_AC_MAX_SNK] = {0}; + struct bt_bap_ep *src_eps[GMAP_AC_MAX_SRC] = {0}; + size_t snk_stream_cnt = 0U; + size_t src_stream_cnt = 0U; + size_t stream_cnt = 0U; + size_t snk_ep_cnt = 0U; + size_t src_ep_cnt = 0U; + + for (size_t i = 0U; i < param->conn_cnt; i++) { +#if UNICAST_SINK_SUPPORTED + for (size_t j = 0U; j < param->snk_cnt[i]; j++) { + snk_eps[snk_ep_cnt] = sink_eps[bt_conn_index(connected_conns[i])][j]; + if (snk_eps[snk_ep_cnt] == NULL) { + FAIL("No sink[%zu][%zu] endpoint available\n", i, j); + + return -ENODEV; + } + snk_ep_cnt++; + } +#endif /* UNICAST_SINK_SUPPORTED */ + +#if UNICAST_SRC_SUPPORTED + for (size_t j = 0U; j < param->src_cnt[i]; j++) { + src_eps[src_ep_cnt] = source_eps[bt_conn_index(connected_conns[i])][j]; + if (src_eps[src_ep_cnt] == NULL) { + FAIL("No source[%zu][%zu] endpoint available\n", i, j); + + return -ENODEV; + } + src_ep_cnt++; + } +#endif /* UNICAST_SRC_SUPPORTED > 0 */ + } + + if (snk_ep_cnt != snk_cnt) { + FAIL("Sink endpoint and stream count mismatch: %zu != %zu\n", snk_ep_cnt, snk_cnt); + + return -EINVAL; + } + + if (src_ep_cnt != src_cnt) { + FAIL("Source endpoint and stream count mismatch: %zu != %zu\n", src_ep_cnt, + src_cnt); + + return -EINVAL; + } + + /* Setup arrays of parameters based on the preset for easier access. This also copies the + * preset so that we can modify them (e.g. update the metadata) + */ + for (size_t i = 0U; i < snk_cnt; i++) { + snk_cap_streams[i] = &snk_uni_streams[i]->stream; + snk_qos[i] = &snk_uni_streams[i]->qos; + snk_codecs[i] = &snk_uni_streams[i]->codec; + } + + for (size_t i = 0U; i < src_cnt; i++) { + src_cap_streams[i] = &src_uni_streams[i]->stream; + src_qos[i] = &src_uni_streams[i]->qos; + src_codecs[i] = &src_uni_streams[i]->codec; + } + + /* CAP Start */ + for (size_t i = 0U; i < param->conn_cnt; i++) { + for (size_t j = 0U; j < param->snk_cnt[i]; j++) { + struct bt_cap_unicast_audio_start_stream_param *stream_param = + &stream_params[stream_cnt]; + + stream_param->member.member = connected_conns[i]; + stream_param->codec = snk_codecs[snk_stream_cnt]; + stream_param->ep = snk_eps[snk_stream_cnt]; + stream_param->qos = snk_qos[snk_stream_cnt]; + stream_param->stream = snk_cap_streams[snk_stream_cnt]; + + snk_stream_cnt++; + stream_cnt++; + + if (param->conn_cnt > 1) { + set_chan_alloc(BIT(i), stream_param->codec); + } + } + + for (size_t j = 0U; j < param->src_cnt[i]; j++) { + struct bt_cap_unicast_audio_start_stream_param *stream_param = + &stream_params[stream_cnt]; + + stream_param->member.member = connected_conns[i]; + stream_param->codec = src_codecs[src_stream_cnt]; + stream_param->ep = src_eps[src_stream_cnt]; + stream_param->qos = src_qos[src_stream_cnt]; + stream_param->stream = src_cap_streams[src_stream_cnt]; + + src_stream_cnt++; + stream_cnt++; + + if (param->conn_cnt > 1) { + set_chan_alloc(BIT(i), stream_param->codec); + } + } + } + + start_param.stream_params = stream_params; + start_param.count = stream_cnt; + start_param.type = BT_CAP_SET_TYPE_AD_HOC; + + return bt_cap_initiator_unicast_audio_start(&start_param, unicast_group); +} + +static int gmap_ac_unicast(const struct gmap_ac_param *param, + struct bt_bap_unicast_group **unicast_group) +{ + /* Allocate params large enough for any params, but only use what is required */ + struct unicast_stream *snk_uni_streams[GMAP_AC_MAX_SNK]; + struct unicast_stream *src_uni_streams[GMAP_AC_MAX_SRC]; + size_t snk_cnt = 0; + size_t src_cnt = 0; + int err; + + if (param->conn_cnt > GMAP_AC_MAX_CONN) { + FAIL("Invalid conn_cnt: %zu\n", param->conn_cnt); + + return -EINVAL; + } + + for (size_t i = 0; i < param->conn_cnt; i++) { + /* Verify conn values */ + if (param->snk_cnt[i] > GMAP_AC_MAX_SNK) { + FAIL("Invalid conn_snk_cnt[%zu]: %zu\n", i, param->snk_cnt[i]); + + return -EINVAL; + } + + if (param->src_cnt[i] > GMAP_AC_MAX_SRC) { + FAIL("Invalid conn_src_cnt[%zu]: %zu\n", i, param->src_cnt[i]); + + return -EINVAL; + } + } + + /* Set all endpoints from multiple connections in a single array, and verify that the known + * endpoints matches the audio configuration + */ + for (size_t i = 0U; i < param->conn_cnt; i++) { + for (size_t j = 0U; j < param->snk_cnt[i]; j++) { + snk_cnt++; + } + + for (size_t j = 0U; j < param->src_cnt[i]; j++) { + src_cnt++; + } + } + + /* Setup arrays of parameters based on the preset for easier access. This also copies the + * preset so that we can modify them (e.g. update the metadata) + */ + for (size_t i = 0U; i < snk_cnt; i++) { + snk_uni_streams[i] = &unicast_streams[i]; + + copy_unicast_stream_preset(snk_uni_streams[i], param->snk_named_preset); + + /* Some audio configuration requires multiple sink channels, + * so multiply the SDU based on the channel count + */ + snk_uni_streams[i]->qos.sdu *= param->snk_chan_cnt; + } + + for (size_t i = 0U; i < src_cnt; i++) { + src_uni_streams[i] = &unicast_streams[i + snk_cnt]; + copy_unicast_stream_preset(src_uni_streams[i], param->src_named_preset); + } + + err = gmap_ac_create_unicast_group(param, snk_uni_streams, snk_cnt, src_uni_streams, + src_cnt, unicast_group); + if (err != 0) { + FAIL("Failed to create group: %d\n", err); + + return err; + } + + UNSET_FLAG(flag_started); + + printk("Starting %zu streams for %s\n", snk_cnt + src_cnt, param->name); + err = gmap_ac_cap_unicast_start(param, snk_uni_streams, snk_cnt, src_uni_streams, src_cnt, + *unicast_group); + if (err != 0) { + FAIL("Failed to start unicast audio: %d\n\n", err); + + return err; + } + + WAIT_FOR_FLAG(flag_started); + + return 0; +} + +static void unicast_audio_stop(struct bt_bap_unicast_group *unicast_group) +{ + int err; + + UNSET_FLAG(flag_stopped); + + err = bt_cap_initiator_unicast_audio_stop(unicast_group); + if (err != 0) { + FAIL("Failed to start unicast audio: %d\n", err); + return; + } + + WAIT_FOR_FLAG(flag_stopped); +} + +static void unicast_group_delete(struct bt_bap_unicast_group *unicast_group) +{ + int err; + + err = bt_bap_unicast_group_delete(unicast_group); + if (err != 0) { + FAIL("Failed to create group: %d\n", err); + return; + } +} + +static void test_ugg(const struct gmap_ac_param *param) +{ + struct bt_bap_unicast_group *unicast_group; + + printk("Running test for %s with Sink Preset %s and Source Preset %s\n", param->name, + param->snk_named_preset != NULL ? param->snk_named_preset->name : "None", + param->src_named_preset != NULL ? param->src_named_preset->name : "None"); + + if (param->conn_cnt > GMAP_AC_MAX_CONN) { + FAIL("Invalid conn_cnt: %zu\n", param->conn_cnt); + return; + } + + init(); + + for (size_t i = 0U; i < param->conn_cnt; i++) { + UNSET_FLAG(flag_mtu_exchanged); + + scan_and_connect(); + + WAIT_FOR_FLAG(flag_mtu_exchanged); + + printk("Connected %zu/%zu\n", i + 1, param->conn_cnt); + } + + if (connected_conn_cnt < param->conn_cnt) { + FAIL("Only %zu/%u connected devices, please connect additional devices for this " + "audio configuration\n", + connected_conn_cnt, param->conn_cnt); + return; + } + + for (size_t i = 0U; i < param->conn_cnt; i++) { + discover_cas(connected_conns[i]); + + if (param->snk_cnt[i] > 0U) { + discover_sink(connected_conns[i]); + } + + if (param->src_cnt[i] > 0U) { + discover_source(connected_conns[i]); + } + } + + gmap_ac_unicast(param, &unicast_group); + + unicast_audio_stop(unicast_group); + + unicast_group_delete(unicast_group); + unicast_group = NULL; + + for (size_t i = 0U; i < param->conn_cnt; i++) { + const int err = + bt_conn_disconnect(connected_conns[i], BT_HCI_ERR_REMOTE_USER_TERM_CONN); + + if (err != 0) { + FAIL("Failed to disconnect conn[%zu]: %d\n", i, err); + } + + bt_conn_unref(connected_conns[i]); + connected_conns[i] = NULL; + } + + PASS("GMAP UGG passed for %s with Sink Preset %s and Source Preset %s\n", param->name, + param->snk_named_preset != NULL ? param->snk_named_preset->name : "None", + param->src_named_preset != NULL ? param->src_named_preset->name : "None"); +} + +static void test_gmap_ac_1(void) +{ + const struct gmap_ac_param param = { + .name = "ac_1", + .conn_cnt = 1U, + .snk_cnt = {1U}, + .src_cnt = {0U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = NULL, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_2(void) +{ + const struct gmap_ac_param param = { + .name = "ac_2", + .conn_cnt = 1U, + .snk_cnt = {0U}, + .src_cnt = {1U}, + .snk_chan_cnt = 1U, + .snk_named_preset = NULL, + .src_named_preset = src_named_preset, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_3(void) +{ + const struct gmap_ac_param param = { + .name = "ac_3", + .conn_cnt = 1U, + .snk_cnt = {1U}, + .src_cnt = {1U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = src_named_preset, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_4(void) +{ + const struct gmap_ac_param param = { + .name = "ac_4", + .conn_cnt = 1U, + .snk_cnt = {1U}, + .src_cnt = {0U}, + .snk_chan_cnt = 2U, + .snk_named_preset = snk_named_preset, + .src_named_preset = NULL, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_5(void) +{ + const struct gmap_ac_param param = { + .name = "ac_5", + .conn_cnt = 1U, + .snk_cnt = {1U}, + .src_cnt = {1U}, + .snk_chan_cnt = 2U, + .snk_named_preset = snk_named_preset, + .src_named_preset = src_named_preset, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_6_i(void) +{ + const struct gmap_ac_param param = { + .name = "ac_6_i", + .conn_cnt = 1U, + .snk_cnt = {2U}, + .src_cnt = {0U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = NULL, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_6_ii(void) +{ + const struct gmap_ac_param param = { + .name = "ac_6_ii", + .conn_cnt = 2U, + .snk_cnt = {1U, 1U}, + .src_cnt = {0U, 0U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = NULL, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_7_ii(void) +{ + const struct gmap_ac_param param = { + .name = "ac_7_ii", + .conn_cnt = 2U, + .snk_cnt = {1U, 0U}, + .src_cnt = {0U, 1U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = src_named_preset, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_8_i(void) +{ + const struct gmap_ac_param param = { + .name = "ac_8_i", + .conn_cnt = 1U, + .snk_cnt = {2U}, + .src_cnt = {1U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = src_named_preset, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_8_ii(void) +{ + const struct gmap_ac_param param = { + .name = "ac_8_ii", + .conn_cnt = 2U, + .snk_cnt = {1U, 1U}, + .src_cnt = {1U, 0U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = src_named_preset, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_11_i(void) +{ + const struct gmap_ac_param param = { + .name = "ac_11_i", + .conn_cnt = 1U, + .snk_cnt = {2U}, + .src_cnt = {2U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = src_named_preset, + }; + + test_ugg(¶m); +} + +static void test_gmap_ac_11_ii(void) +{ + const struct gmap_ac_param param = { + .name = "ac_11_ii", + .conn_cnt = 2U, + .snk_cnt = {1U, 1U}, + .src_cnt = {1U, 1U}, + .snk_chan_cnt = 1U, + .snk_named_preset = snk_named_preset, + .src_named_preset = src_named_preset, + }; + + test_ugg(¶m); +} + +static void test_args(int argc, char *argv[]) +{ + for (size_t argn = 0; argn < argc; argn++) { + const char *arg = argv[argn]; + + if (strcmp(arg, "sink_preset") == 0) { + snk_named_preset = + gmap_get_named_preset(true, BT_AUDIO_DIR_SINK, argv[++argn]); + } else if (strcmp(arg, "source_preset") == 0) { + src_named_preset = + gmap_get_named_preset(true, BT_AUDIO_DIR_SOURCE, argv[++argn]); + } else { + FAIL("Invalid arg: %s\n", arg); + } + } +} + +static const struct bst_test_instance test_gmap_ugg[] = { + { + .test_id = "gmap_ugg_ac_1", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_1, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_2", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_2, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_3", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_3, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_4", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_4, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_5", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_5, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_6_i", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_6_i, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_6_ii", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_6_ii, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_7_ii", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_7_ii, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_8_i", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_8_i, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_8_ii", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_8_ii, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_11_i", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_11_i, + .test_args_f = test_args, + }, + { + .test_id = "gmap_ugg_ac_11_ii", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_gmap_ac_11_ii, + .test_args_f = test_args, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_gmap_ugg_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_gmap_ugg); +} + +#else /* !(CONFIG_BT_GMAP_UGG) */ + +struct bst_test_list *test_gmap_ugg_install(struct bst_test_list *tests) +{ + return tests; +} + +#endif /* CONFIG_BT_GMAP_UGG */ diff --git a/tests/bsim/bluetooth/audio/src/gmap_ugt_test.c b/tests/bsim/bluetooth/audio/src/gmap_ugt_test.c new file mode 100644 index 000000000000000..65ce796b94bcacc --- /dev/null +++ b/tests/bsim/bluetooth/audio/src/gmap_ugt_test.c @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_BT_CAP_ACCEPTOR) + +#include +#include +#include +#include +#include "common.h" +#include "bap_common.h" + +extern enum bst_result_t bst_result; + +#define CONTEXT (BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | BT_AUDIO_CONTEXT_TYPE_GAME) +#define LOCATION (BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT) + +static uint8_t csis_rank = 1; +static struct bt_bap_lc3_preset unicast_preset_16_2_1 = + BT_BAP_LC3_UNICAST_PRESET_16_2_1(LOCATION, CONTEXT); + +static const struct bt_codec_qos_pref unicast_qos_pref = + BT_CODEC_QOS_PREF(true, BT_GAP_LE_PHY_2M, 0U, 60U, 10000U, 40000U, 10000U, 40000U); + +#define UNICAST_CHANNEL_COUNT_1 BIT(0) + +static struct bt_cap_stream + unicast_streams[CONFIG_BT_ASCS_ASE_SNK_COUNT + CONFIG_BT_ASCS_ASE_SRC_COUNT]; + +CREATE_FLAG(flag_unicast_stream_started); + +static void unicast_stream_enabled_cb(struct bt_bap_stream *stream) +{ + struct bt_bap_ep_info ep_info; + int err; + + printk("Enabled: stream %p\n", stream); + + err = bt_bap_ep_get_info(stream->ep, &ep_info); + if (err != 0) { + FAIL("Failed to get ep info: %d\n", err); + return; + } + + if (ep_info.dir == BT_AUDIO_DIR_SINK) { + /* Automatically do the receiver start ready operation */ + err = bt_bap_stream_start(stream); + + if (err != 0) { + FAIL("Failed to start stream: %d\n", err); + return; + } + } +} + +static void unicast_stream_started_cb(struct bt_bap_stream *stream) +{ + printk("Started: stream %p\n", stream); + SET_FLAG(flag_unicast_stream_started); +} + +static struct bt_bap_stream_ops unicast_stream_ops = { + .enabled = unicast_stream_enabled_cb, + .started = unicast_stream_started_cb, +}; + +/* TODO: Expand with GMAP service data */ +static const struct bt_data gmap_acceptor_ad[] = { + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_CAS_VAL)), +}; + +static struct bt_csip_set_member_svc_inst *csip_set_member; + +static struct bt_bap_stream *unicast_stream_alloc(void) +{ + for (size_t i = 0; i < ARRAY_SIZE(unicast_streams); i++) { + struct bt_bap_stream *stream = &unicast_streams[i].bap_stream; + + if (!stream->conn) { + return stream; + } + } + + return NULL; +} + +static int unicast_server_config(struct bt_conn *conn, const struct bt_bap_ep *ep, + enum bt_audio_dir dir, const struct bt_codec *codec, + struct bt_bap_stream **stream, + struct bt_codec_qos_pref *const pref, struct bt_bap_ascs_rsp *rsp) +{ + printk("ASE Codec Config: conn %p ep %p dir %u\n", conn, ep, dir); + + print_codec(codec); + + *stream = unicast_stream_alloc(); + if (*stream == NULL) { + printk("No streams available\n"); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, BT_BAP_ASCS_REASON_NONE); + + return -ENOMEM; + } + + printk("ASE Codec Config stream %p\n", *stream); + + *pref = unicast_qos_pref; + + return 0; +} + +static int unicast_server_reconfig(struct bt_bap_stream *stream, enum bt_audio_dir dir, + const struct bt_codec *codec, + struct bt_codec_qos_pref *const pref, + struct bt_bap_ascs_rsp *rsp) +{ + printk("ASE Codec Reconfig: stream %p\n", stream); + + print_codec(codec); + + *pref = unicast_qos_pref; + + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED, BT_BAP_ASCS_REASON_NONE); + + /* We only support one QoS at the moment, reject changes */ + return -ENOEXEC; +} + +static int unicast_server_qos(struct bt_bap_stream *stream, const struct bt_codec_qos *qos, + struct bt_bap_ascs_rsp *rsp) +{ + printk("QoS: stream %p qos %p\n", stream, qos); + + print_qos(qos); + + return 0; +} + +static int unicast_server_enable(struct bt_bap_stream *stream, const struct bt_codec_data *meta, + size_t meta_count, struct bt_bap_ascs_rsp *rsp) +{ + printk("Enable: stream %p meta_count %zu\n", stream, meta_count); + + return 0; +} + +static int unicast_server_start(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) +{ + printk("Start: stream %p\n", stream); + + return 0; +} + +static int unicast_server_metadata(struct bt_bap_stream *stream, const struct bt_codec_data *meta, + size_t meta_count, struct bt_bap_ascs_rsp *rsp) +{ + printk("Metadata: stream %p meta_count %zu\n", stream, meta_count); + + for (size_t i = 0; i < meta_count; i++) { + const struct bt_codec_data *data = &meta[i]; + + if (!valid_metadata_type(data->data.type, data->data.data_len)) { + printk("Invalid metadata type %u or length %u\n", data->data.type, + data->data.data_len); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, + data->data.type); + return -EINVAL; + } + } + + return 0; +} + +static int unicast_server_disable(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) +{ + printk("Disable: stream %p\n", stream); + + return 0; +} + +static int unicast_server_stop(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) +{ + printk("Stop: stream %p\n", stream); + + return 0; +} + +static int unicast_server_release(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp) +{ + printk("Release: stream %p\n", stream); + + return 0; +} + +static struct bt_bap_unicast_server_cb unicast_server_cbs = { + .config = unicast_server_config, + .reconfig = unicast_server_reconfig, + .qos = unicast_server_qos, + .enable = unicast_server_enable, + .start = unicast_server_start, + .metadata = unicast_server_metadata, + .disable = unicast_server_disable, + .stop = unicast_server_stop, + .release = unicast_server_release, +}; + +static void set_location(void) +{ + int err; + + if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC)) { + err = bt_pacs_set_location(BT_AUDIO_DIR_SINK, LOCATION); + if (err != 0) { + FAIL("Failed to set sink location (err %d)\n", err); + return; + } + } + + if (IS_ENABLED(CONFIG_BT_PAC_SRC_LOC)) { + err = bt_pacs_set_location(BT_AUDIO_DIR_SOURCE, LOCATION); + if (err != 0) { + FAIL("Failed to set source location (err %d)\n", err); + return; + } + } + + printk("Location successfully set\n"); +} + +static int set_supported_contexts(void) +{ + int err; + + if (IS_ENABLED(CONFIG_BT_PAC_SNK)) { + err = bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SINK, CONTEXT); + if (err != 0) { + printk("Failed to set sink supported contexts (err %d)\n", err); + + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_PAC_SRC)) { + err = bt_pacs_set_supported_contexts(BT_AUDIO_DIR_SOURCE, CONTEXT); + if (err != 0) { + printk("Failed to set source supported contexts (err %d)\n", err); + + return err; + } + } + + printk("Supported contexts successfully set\n"); + + return 0; +} + +static void set_available_contexts(void) +{ + int err; + + err = bt_pacs_set_available_contexts(BT_AUDIO_DIR_SINK, CONTEXT); + if (IS_ENABLED(CONFIG_BT_PAC_SNK) && err != 0) { + FAIL("Failed to set sink available contexts (err %d)\n", err); + return; + } + + err = bt_pacs_set_available_contexts(BT_AUDIO_DIR_SOURCE, CONTEXT); + if (IS_ENABLED(CONFIG_BT_PAC_SRC) && err != 0) { + FAIL("Failed to set source available contexts (err %d)\n", err); + return; + } + + printk("Available contexts successfully set\n"); +} + +static void test_main(void) +{ + /* TODO: Register all GMAP codec capabilities */ + static struct bt_pacs_cap unicast_cap = { + .codec = &unicast_preset_16_2_1.codec, + }; + int err; + + err = bt_enable(NULL); + if (err != 0) { + FAIL("Bluetooth enable failed (err %d)\n", err); + return; + } + + printk("Bluetooth initialized\n"); + + if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER)) { + const struct bt_csip_set_member_register_param csip_set_member_param = { + .set_size = 2, + .rank = csis_rank, + .lockable = true, + /* Using the CSIP_SET_MEMBER test sample SIRK */ + .set_sirk = {0xcd, 0xcc, 0x72, 0xdd, 0x86, 0x8c, 0xcd, 0xce, 0x22, 0xfd, + 0xa1, 0x21, 0x09, 0x7d, 0x7d, 0x45}, + }; + + err = bt_cap_acceptor_register(&csip_set_member_param, &csip_set_member); + if (err != 0) { + FAIL("CAP acceptor failed to register (err %d)\n", err); + return; + } + } + + err = bt_pacs_cap_register(BT_AUDIO_DIR_SINK, &unicast_cap); + if (err != 0) { + FAIL("Capability register failed (err %d)\n", err); + + return; + } + + err = bt_pacs_cap_register(BT_AUDIO_DIR_SOURCE, &unicast_cap); + if (err != 0) { + FAIL("Capability register failed (err %d)\n", err); + + return; + } + + err = bt_bap_unicast_server_register_cb(&unicast_server_cbs); + if (err != 0) { + FAIL("Failed to register unicast server callbacks (err %d)\n", err); + + return; + } + + for (size_t i = 0U; i < ARRAY_SIZE(unicast_streams); i++) { + bt_cap_stream_ops_register(&unicast_streams[i], &unicast_stream_ops); + } + + set_supported_contexts(); + set_available_contexts(); + set_location(); + + err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, gmap_acceptor_ad, ARRAY_SIZE(gmap_acceptor_ad), + NULL, 0); + if (err != 0) { + FAIL("Advertising failed to start (err %d)\n", err); + return; + } + + WAIT_FOR_FLAG(flag_connected); + + WAIT_FOR_FLAG(flag_disconnected); + + PASS("GMAP UGT passed\n"); +} + +static void test_args(int argc, char *argv[]) +{ + for (size_t argn = 0; argn < argc; argn++) { + const char *arg = argv[argn]; + + if (strcmp(arg, "rank") == 0) { + csis_rank = strtoul(argv[++argn], NULL, 10); + } else { + FAIL("Invalid arg: %s\n", arg); + } + } +} + +static const struct bst_test_instance test_gmap_ugt[] = { + { + .test_id = "gmap_ugt", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_main, + .test_args_f = test_args, + }, + BSTEST_END_MARKER, +}; + +struct bst_test_list *test_gmap_ugt_install(struct bst_test_list *tests) +{ + return bst_add_tests(tests, test_gmap_ugt); +} + +#else /* !(CONFIG_BT_CAP_ACCEPTOR) */ + +struct bst_test_list *test_gmap_ugt_install(struct bst_test_list *tests) +{ + return tests; +} + +#endif /* CONFIG_BT_CAP_ACCEPTOR */ diff --git a/tests/bsim/bluetooth/audio/src/main.c b/tests/bsim/bluetooth/audio/src/main.c index 40e0c116a6554ae..3298f104f20a8b7 100644 --- a/tests/bsim/bluetooth/audio/src/main.c +++ b/tests/bsim/bluetooth/audio/src/main.c @@ -33,6 +33,8 @@ extern struct bst_test_list *test_ias_install(struct bst_test_list *tests); extern struct bst_test_list *test_ias_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_tmap_client_install(struct bst_test_list *tests); extern struct bst_test_list *test_tmap_server_install(struct bst_test_list *tests); +extern struct bst_test_list *test_gmap_ugg_install(struct bst_test_list *tests); +extern struct bst_test_list *test_gmap_ugt_install(struct bst_test_list *tests); bst_test_install_t test_installers[] = { test_vcp_install, @@ -62,7 +64,9 @@ bst_test_install_t test_installers[] = { test_ias_client_install, test_tmap_server_install, test_tmap_client_install, - NULL + test_gmap_ugg_install, + test_gmap_ugt_install, + NULL, }; int main(void) diff --git a/tests/bsim/bluetooth/audio/test_scripts/_gmap.sh b/tests/bsim/bluetooth/audio/test_scripts/_gmap.sh new file mode 100755 index 000000000000000..175e453da1ba49e --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/_gmap.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +dir_path=$(dirname "$0") + +set -e # Exit on error + +$dir_path/gmap_unicast_ac_1.sh +$dir_path/gmap_unicast_ac_2.sh +$dir_path/gmap_unicast_ac_3.sh +$dir_path/gmap_unicast_ac_4.sh +$dir_path/gmap_unicast_ac_5.sh +$dir_path/gmap_unicast_ac_6_i.sh +$dir_path/gmap_unicast_ac_6_ii.sh +$dir_path/gmap_unicast_ac_7_ii.sh +$dir_path/gmap_unicast_ac_8_i.sh +$dir_path/gmap_unicast_ac_8_ii.sh +$dir_path/gmap_unicast_ac_11_i.sh +$dir_path/gmap_unicast_ac_11_ii.sh diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_1.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_1.sh new file mode 100755 index 000000000000000..57edc1901cb2480 --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_1.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_1" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_1() { + printf "\n\n======== Running GMAP AC_1 with %s =========\n\n" $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_1 -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 ${@:2} + + wait_for_background_jobs +} + +set -e # Exit on error + +Execute_AC_1 32_1_gr +Execute_AC_1 32_2_gr +Execute_AC_1 48_1_gr +Execute_AC_1 48_2_gr +Execute_AC_1 48_3_gr +Execute_AC_1 48_4_gr diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_11_i.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_11_i.sh new file mode 100755 index 000000000000000..c7643aa05eec581 --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_11_i.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_11_i" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_11_I() { + printf "\n\n======== Running GMAP AC_11_I with %s and %s =========\n\n" $1 $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_11_i -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 source_preset $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 ${@:3} + + wait_for_background_jobs +} + +set -e # Exit on error + +Execute_AC_11_I 32_1_gr 32_1_gs +Execute_AC_11_I 32_2_gr 32_2_gs +Execute_AC_11_I 48_1_gr 32_1_gs +Execute_AC_11_I 48_2_gr 32_2_gs +Execute_AC_11_I 48_1_gr 48_1_gs +Execute_AC_11_I 48_2_gr 48_2_gs +Execute_AC_11_I 48_3_gr 32_1_gs +Execute_AC_11_I 48_4_gr 32_2_gs +Execute_AC_11_I 48_3_gr 48_1_gs +Execute_AC_11_I 48_4_gr 48_2_gs diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_11_ii.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_11_ii.sh new file mode 100755 index 000000000000000..ef9d8c4eb4ba44b --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_11_ii.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_11_ii" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_11_II() { + printf "\n\n======== Running GMAP AC_11_II with %s and %s =========\n\n" $1 $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_11_ii -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 source_preset $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=2 -testid=gmap_ugt -RealEncryption=1 -rs=69 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=3 -sim_length=60e6 ${@:3} + + wait_for_background_jobs +} + +set -e # Exit on error + +# Execute_AC_11_II 32_1_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_11_II 32_2_gr 32_2_gs +# Execute_AC_11_II 48_1_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_11_II 48_2_gr 32_2_gs +# Execute_AC_11_II 48_1_gr 48_1_gs # CIS disconnect with reason 0x3e +Execute_AC_11_II 48_2_gr 48_2_gs +# Execute_AC_11_II 48_3_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_11_II 48_4_gr 32_2_gs +# Execute_AC_11_II 48_3_gr 48_1_gs # CIS disconnect with reason 0x3e +Execute_AC_11_II 48_4_gr 48_2_gs diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_2.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_2.sh new file mode 100755 index 000000000000000..2f9ef8de903d8b2 --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_2.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_2" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + + +function Execute_AC_2() { + printf "\n\n======== Running GMAP AC_2 with %s =========\n\n" $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_2 -RealEncryption=1 \ + -rs=23 -argstest source_preset $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 ${@:2} + + wait_for_background_jobs +} + +set -e # Exit on error + +Execute_AC_2 32_1_gs +Execute_AC_2 32_2_gs +Execute_AC_2 48_1_gs +Execute_AC_2 48_2_gs diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_3.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_3.sh new file mode 100755 index 000000000000000..ad854170061d183 --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_3.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_3" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + + +function Execute_AC_3() { + printf "\n\n======== Running GMAP AC_3 with %s and %s =========\n\n" $1 $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_3 -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 source_preset $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 ${@:3} + + wait_for_background_jobs +} + +set -e # Exit on error + +Execute_AC_3 32_1_gr 32_1_gs +Execute_AC_3 32_2_gr 32_2_gs +Execute_AC_3 48_1_gr 32_1_gs +Execute_AC_3 48_2_gr 32_2_gs +Execute_AC_3 48_1_gr 48_1_gs +Execute_AC_3 48_2_gr 48_2_gs +Execute_AC_3 48_3_gr 32_1_gs +Execute_AC_3 48_4_gr 32_2_gs +Execute_AC_3 48_3_gr 48_1_gs +Execute_AC_3 48_4_gr 48_2_gs diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_4.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_4.sh new file mode 100755 index 000000000000000..dc03bc17e51f043 --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_4.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_4" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_4() { + printf "\n\n======== Running GMAP AC_4 with %s =========\n\n" $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_4 -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 ${@:2} + + wait_for_background_jobs +} + +Execute_AC_4 32_1_gr +Execute_AC_4 32_2_gr +Execute_AC_4 48_1_gr +Execute_AC_4 48_2_gr +Execute_AC_4 48_3_gr +Execute_AC_4 48_4_gr diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_5.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_5.sh new file mode 100755 index 000000000000000..748db7aba18e72c --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_5.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_5" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + + +function Execute_AC_5() { + printf "\n\n======== Running GMAP AC_5 with %s and %s =========\n\n" $1 $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_5 -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 source_preset $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 ${@:3} + + wait_for_background_jobs +} + +Execute_AC_5 32_1_gr 32_1_gs +Execute_AC_5 32_2_gr 32_2_gs +Execute_AC_5 48_1_gr 32_1_gs +Execute_AC_5 48_2_gr 32_2_gs +Execute_AC_5 48_1_gr 48_1_gs +Execute_AC_5 48_2_gr 48_2_gs +Execute_AC_5 48_3_gr 32_1_gs +Execute_AC_5 48_4_gr 32_2_gs diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_6_i.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_6_i.sh new file mode 100755 index 000000000000000..3c20ad43d10d2fc --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_6_i.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_6_i" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_6_I() { + printf "\n\n======== Running GMAP AC_6_I with %s =========\n\n" $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_6_i -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 ${@:2} + + wait_for_background_jobs +} + +Execute_AC_6_I 32_1_gr +Execute_AC_6_I 32_2_gr +Execute_AC_6_I 48_1_gr +Execute_AC_6_I 48_2_gr +Execute_AC_6_I 48_3_gr +Execute_AC_6_I 48_4_gr diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_6_ii.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_6_ii.sh new file mode 100755 index 000000000000000..a4d61ba7bd829a1 --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_6_ii.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_6_ii" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_6_II() { + printf "\n\n======== Running GMAP AC_6_II with %s =========\n\n" $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_6_ii -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=2 -testid=gmap_ugt -RealEncryption=1 -rs=69 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=3 -sim_length=60e6 ${@:2} + + wait_for_background_jobs +} + +set -e # Exit on error + +# Execute_AC_6_II 32_1_gr # CIS disconnect with reason 0x3e +Execute_AC_6_II 32_2_gr +# Execute_AC_6_II 48_1_gr # CIS disconnect with reason 0x3e +Execute_AC_6_II 48_2_gr +# Execute_AC_6_II 48_3_gr # CIS disconnect with reason 0x3e +Execute_AC_6_II 48_4_gr diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_7_ii.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_7_ii.sh new file mode 100755 index 000000000000000..02204514cf5da5f --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_7_ii.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_7_ii" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_7_II() { + printf "\n\n======== Running GMAP AC_7_II with %s and %s =========\n\n" $1 $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_7_ii -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 source_preset $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=2 -testid=gmap_ugt -RealEncryption=1 -rs=69 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=3 -sim_length=60e6 ${@:3} + + wait_for_background_jobs +} + +set -e # Exit on error + +# Execute_AC_7_II 32_1_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_7_II 32_2_gr 32_2_gs +# Execute_AC_7_II 48_1_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_7_II 48_2_gr 32_2_gs +# Execute_AC_7_II 48_1_gr 48_1_gs # CIS disconnect with reason 0x3e +Execute_AC_7_II 48_2_gr 48_2_gs +# Execute_AC_7_II 48_3_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_7_II 48_4_gr 32_2_gs +# Execute_AC_7_II 48_3_gr 48_1_gs # CIS disconnect with reason 0x3e +Execute_AC_7_II 48_4_gr 48_2_gs diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_8_i.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_8_i.sh new file mode 100755 index 000000000000000..1933f4b7b362adc --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_8_i.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_8_i" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_8_I() { + printf "\n\n======== Running GMAP AC_8_I with %s and %s =========\n\n" $1 $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_8_i -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 source_preset $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=2 -sim_length=60e6 ${@:3} + + wait_for_background_jobs +} + +set -e # Exit on error + +Execute_AC_8_I 32_1_gr 32_1_gs +Execute_AC_8_I 32_2_gr 32_2_gs +Execute_AC_8_I 48_1_gr 32_1_gs +Execute_AC_8_I 48_2_gr 32_2_gs +Execute_AC_8_I 48_1_gr 48_1_gs +Execute_AC_8_I 48_2_gr 48_2_gs +Execute_AC_8_I 48_3_gr 32_1_gs +Execute_AC_8_I 48_4_gr 32_2_gs +Execute_AC_8_I 48_3_gr 48_1_gs +Execute_AC_8_I 48_4_gr 48_2_gs diff --git a/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_8_ii.sh b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_8_ii.sh new file mode 100755 index 000000000000000..ce7dd33598b10fc --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/gmap_unicast_ac_8_ii.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="gmap_unicast_ac_8_ii" +VERBOSITY_LEVEL=2 +EXECUTE_TIMEOUT=60 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +function Execute_AC_8_II() { + printf "\n\n======== Running GMAP AC_8_II with %s and %s =========\n\n" $1 $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=gmap_ugg_ac_8_ii -RealEncryption=1 \ + -rs=23 -argstest sink_preset $1 source_preset $2 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=gmap_ugt -RealEncryption=1 -rs=46 + + Execute ./bs_${BOARD}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=2 -testid=gmap_ugt -RealEncryption=1 -rs=69 + + # Simulation time should be larger than the WAIT_TIME in common.h + Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=3 -sim_length=60e6 ${@:3} + + wait_for_background_jobs +} + +set -e # Exit on error + +# Execute_AC_8_II 32_1_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_8_II 32_2_gr 32_2_gs +# Execute_AC_8_II 48_1_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_8_II 48_2_gr 32_2_gs +# Execute_AC_8_II 48_1_gr 48_1_gs # CIS disconnect with reason 0x3e +Execute_AC_8_II 48_2_gr 48_2_gs +# Execute_AC_8_II 48_3_gr 32_1_gs # CIS disconnect with reason 0x3e +Execute_AC_8_II 48_4_gr 32_2_gs +# Execute_AC_8_II 48_3_gr 48_1_gs # CIS disconnect with reason 0x3e +Execute_AC_8_II 48_4_gr 48_2_gs diff --git a/tests/bsim/sh_common.source b/tests/bsim/sh_common.source index c5da7dd2ec6d71f..05c29c79070c3a3 100644 --- a/tests/bsim/sh_common.source +++ b/tests/bsim/sh_common.source @@ -45,4 +45,3 @@ function Execute() { check_program_exists $1 run_in_background timeout -v ${EXECUTE_TIMEOUT} $@ } -