diff --git a/tests/bsim/babblekit/CMakeLists.txt b/tests/bsim/babblekit/CMakeLists.txt new file mode 100644 index 0000000000..67fdbc8bd3 --- /dev/null +++ b/tests/bsim/babblekit/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Helpers that can be used on bsim targets but don't have other +# dependencies (e.g. on Bluetooth, etc). +add_library(babblekit) + +target_link_libraries(babblekit PUBLIC + kernel + zephyr_interface +) + +target_include_directories(babblekit PUBLIC + include +) + +target_sources(babblekit PRIVATE + src/sync.c +) diff --git a/tests/bsim/babblekit/include/babblekit/flags.h b/tests/bsim/babblekit/include/babblekit/flags.h new file mode 100644 index 0000000000..89c8b45ca3 --- /dev/null +++ b/tests/bsim/babblekit/include/babblekit/flags.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * Provides a way to set/clear and block on binary flags. + * + * These flags are often used to wait until the test has gotten in a particular + * state, e.g. a connection is established or a message has been successfully + * sent. + * + * These macros can only be called from Zephyr threads. They can't be called + * from e.g. a bs_tests callback. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/* Declare a flag that has been defined in another file */ +#define DECLARE_FLAG(flag) extern atomic_t flag + +/* Define a new binary flag. + * Declare them static if defining flags with the same name in multiple files. + * + * @param flag Name of the flag + */ +#define DEFINE_FLAG(flag) atomic_t flag = (atomic_t) false + +#define SET_FLAG(flag) (void)atomic_set(&flag, (atomic_t) true) +#define UNSET_FLAG(flag) (void)atomic_set(&flag, (atomic_t) false) + +#define IS_FLAG_SET(flag) (atomic_get(&flag) != false) + +/* Block until `flag` is equal to `val` */ +#define WAIT_FOR_VAL(var, val) \ + while (atomic_get(&var) != val) { \ + (void)k_sleep(K_MSEC(1)); \ + } + +/* Block until `flag` is true */ +#define WAIT_FOR_FLAG(flag) \ + while (!(bool)atomic_get(&flag)) { \ + (void)k_sleep(K_MSEC(1)); \ + } + +/* Block until `flag` is false */ +#define WAIT_FOR_FLAG_UNSET(flag) \ + while ((bool)atomic_get(&flag)) { \ + (void)k_sleep(K_MSEC(1)); \ + } + +/* Block until `flag` is true and set it to false */ +#define TAKE_FLAG(flag) \ + while (!(bool)atomic_cas(&flag, true, false)) { \ + (void)k_sleep(K_MSEC(1)); \ + } diff --git a/tests/bsim/babblekit/include/babblekit/sync.h b/tests/bsim/babblekit/include/babblekit/sync.h new file mode 100644 index 0000000000..d57538ac4d --- /dev/null +++ b/tests/bsim/babblekit/include/babblekit/sync.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + * + * This file provides a synchronization mechanism between devices, for the + * simple use-case when there are only two devices in the simulation. + */ + + +/* + * @brief Initialize the sync library + * + * This initializes a simple synchronization library based on bsim backchannels. + * + * Calling `bk_sync_wait()` on device A will make it block until + * `bk_sync_send()` is called on device B. + * + * @note Only works between two devices in a simulation, with IDs 0 and 1. + * + * @retval 0 Sync channel operational + * @retval -1 Failed to open sync channel + * + */ +int bk_sync_init(void); + +/* + * @brief Send a synchronization packet + * + * @note Only works between two devices in a simulation, with IDs 0 and 1. + * + */ +void bk_sync_send(void); + +/* + * @brief Wait for a synchronization packet + * + * This blocks until the other device has called `bk_sync_send()`. + * + * @note Only works between two devices in a simulation, with IDs 0 and 1. + * + */ +void bk_sync_wait(void); diff --git a/tests/bsim/babblekit/include/babblekit/testcase.h b/tests/bsim/babblekit/include/babblekit/testcase.h new file mode 100644 index 0000000000..9d13a1a003 --- /dev/null +++ b/tests/bsim/babblekit/include/babblekit/testcase.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "bs_tracing.h" +#include "bs_types.h" +#include "bstests.h" + +extern enum bst_result_t bst_result; + +/* + * @brief Mark the test as in progress + * + * Call this at the start of the test entry point. + * + * @param ... format-string and arguments to print to console + * + */ +#define TEST_START(msg, ...) \ + do { \ + bst_result = In_progress; \ + bs_trace_info_time(2, "Test start: " msg "\n", ##__VA_ARGS__); \ + } while (0) + +/* + * @brief Fail the test and exit + * + * Printf-like function that also terminates the device with an error code. + * + * @param ... format-string and arguments to print to console + * + */ +#define TEST_FAIL(msg, ...) \ + do { \ + bst_result = Failed; \ + bs_trace_error_time_line(msg "\n", ##__VA_ARGS__); \ + } while (0) + +/* + * @brief Mark the currently-running test as "Passed" + * + * Mark the test as "passed". The execution continues after that point. + * + * @note Use this if you use backchannels (testlib/bsim/sync.h). + * + * After calling this, the executable will eventually return 0 when it exits. + * Unless `TEST_FAIL` is called (e.g. in a callback). + * + * @param ... format-string and arguments to print to console + * + */ +#define TEST_PASS(msg, ...) \ + do { \ + bst_result = Passed; \ + bs_trace_info_time(2, "Test end: " msg "\n", ##__VA_ARGS__); \ + } while (0) + +/* + * @brief Mark test case as passed and end execution + * + * Mark the role / test-case as "Passed" and return 0. + * + * @note DO NOT use this if you use backchannels (testlib/bsim/sync.h). + * + * @note This macro only ends execution for the current executable, not all + * executables in a simulation. + * + * @param ... format-string and arguments to print to console + * + */ +#define TEST_PASS_AND_EXIT(msg, ...) \ + do { \ + bst_result = Passed; \ + bs_trace_info_time(2, "Test end: " msg "\n", ##__VA_ARGS__); \ + bs_trace_silent_exit(0); \ + } while (0) + +/* + * @brief Assert `expr` is true + * + * Assert that `expr` is true. If assertion is false, print a printf-like + * message to the console and fail the test. I.e. return non-zero. + * + * @note This is different than `sys/__assert.h`. + * + * @param message String to print to console + * + */ +#define TEST_ASSERT(expr, ...) \ + do { \ + if (!(expr)) { \ + TEST_FAIL(__VA_ARGS__); \ + } \ + } while (0) + +/* + * @brief Print a value. Lower-level than `printk` or `LOG_xx`. + * + * Print a message to console. + * + * This can be safely called at any time in the execution of the device. + * Use it to print when the logging subsystem is not available, e.g. early + * startup or shutdown. + * + * @param ... format-string and arguments to print to console + * + */ +#define TEST_PRINT(msg, ...) \ + bs_trace_print(BS_TRACE_INFO, __FILE__, __LINE__, 0, BS_TRACE_AUTOTIME, 0, \ + msg "\n", ##__VA_ARGS__) diff --git a/tests/bsim/babblekit/src/sync.c b/tests/bsim/babblekit/src/sync.c new file mode 100644 index 0000000000..6a21242007 --- /dev/null +++ b/tests/bsim/babblekit/src/sync.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "argparse.h" +#include "bs_types.h" +#include "bs_tracing.h" +#include "time_machine.h" +#include "bs_pc_backchannel.h" + +#include +LOG_MODULE_REGISTER(sync, CONFIG_LOG_DEFAULT_LEVEL); + +#define CHANNEL_ID 0 +#define MSG_SIZE 1 + +int bk_sync_init(void) +{ + uint device_number = get_device_nbr(); + uint peer_number = device_number ^ 1; + uint device_numbers[] = { peer_number }; + uint channel_numbers[] = { CHANNEL_ID }; + uint *ch; + + ch = bs_open_back_channel(device_number, device_numbers, channel_numbers, + ARRAY_SIZE(channel_numbers)); + if (!ch) { + return -1; + } + + LOG_DBG("Sync initialized"); + + return 0; +} + +void bk_sync_send(void) +{ + uint8_t sync_msg[MSG_SIZE] = { get_device_nbr() }; + + LOG_DBG("Sending sync"); + bs_bc_send_msg(CHANNEL_ID, sync_msg, ARRAY_SIZE(sync_msg)); +} + +void bk_sync_wait(void) +{ + uint8_t sync_msg[MSG_SIZE]; + + LOG_DBG("Waiting for sync"); + + while (true) { + if (bs_bc_is_msg_received(CHANNEL_ID) > 0) { + bs_bc_receive_msg(CHANNEL_ID, sync_msg, ARRAY_SIZE(sync_msg)); + if (sync_msg[0] != get_device_nbr()) { + /* Received a message from another device, exit */ + break; + } + } + + k_sleep(K_MSEC(1)); + } + + LOG_DBG("Sync received"); +}