diff --git a/libs/LogKit/include/LogKit.h b/libs/LogKit/include/LogKit.h index ce5fd68b78..32e13f53dd 100644 --- a/libs/LogKit/include/LogKit.h +++ b/libs/LogKit/include/LogKit.h @@ -95,11 +95,32 @@ namespace internal { internal::filehandle->write(data, size); } + inline void disable_filehandle_input() + + { + if (filehandle == nullptr) { + return; + } + + internal::filehandle->enable_input(false); + } + + inline void enable_filehandle_input() + { + if (filehandle == nullptr) { + return; + } + + internal::filehandle->enable_input(true); + } + } // namespace internal [[maybe_unused]] inline void set_filehandle_pointer(filehandle_ptr fh) { internal::filehandle = fh; + + internal::disable_filehandle_input(); } inline void process_fifo() @@ -230,7 +251,11 @@ inline void set_print_function(...) {} // NOSONAR inline void set_filehandle_pointer(...) {} // NOSONAR namespace internal { - inline void default_sink_function(...) {} // NOSONAR + + inline void default_sink_function(...) {} // NOSONAR + inline void disable_filehandle_input(...) {} // NOSONAR + inline void enable_filehandle_input(...) {} // NOSONAR + } // namespace internal #endif // ENABLE_LOG_DEBUG diff --git a/tests/functional/CMakeLists.txt b/tests/functional/CMakeLists.txt index e9fc6e7438..a785d9828f 100644 --- a/tests/functional/CMakeLists.txt +++ b/tests/functional/CMakeLists.txt @@ -39,6 +39,8 @@ endfunction() add_subdirectory(${TESTS_FUNCTIONAL_TESTS_DIR}/boost_ut) +add_subdirectory(${TESTS_FUNCTIONAL_TESTS_DIR}/deep_sleep_log_kit) +add_subdirectory(${TESTS_FUNCTIONAL_TESTS_DIR}/deep_sleep_mbed_hal) add_subdirectory(${TESTS_FUNCTIONAL_TESTS_DIR}/imu) add_subdirectory(${TESTS_FUNCTIONAL_TESTS_DIR}/io_expander) add_subdirectory(${TESTS_FUNCTIONAL_TESTS_DIR}/file_manager) diff --git a/tests/functional/include/tests/utils.h b/tests/functional/include/tests/utils.h index c5a7fcc820..3862ec05b8 100644 --- a/tests/functional/include/tests/utils.h +++ b/tests/functional/include/tests/utils.h @@ -6,9 +6,10 @@ #include "rtos/ThisThread.h" -#include "FATFileSystem.h" +#include "storage/blockdevice/COMPONENT_SD/include/SD/SDBlockDevice.h" +#include "storage/filesystem/fat/include/fat/FATFileSystem.h" + #include "LogKit.h" -#include "SDBlockDevice.h" namespace utils { diff --git a/tests/functional/include/tests/utils_sleep.h b/tests/functional/include/tests/utils_sleep.h new file mode 100644 index 0000000000..bbdb006dda --- /dev/null +++ b/tests/functional/include/tests/utils_sleep.h @@ -0,0 +1,97 @@ +// Leka - LekaOS +// Copyright 2017 ARM Limited +// Copyright 2022 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "hal/lp_ticker_api.h" +#include "hal/ticker_api.h" +#include "hal/us_ticker_api.h" +#include "platform/mbed_power_mgmt.h" +#include "platform/mbed_wait_api.h" +#include "rtos/ThisThread.h" + +// ? utilities taken from https://github.com/ARMmbed/mbed-os +// ? https://github.com/ARMmbed/mbed-os/blob/master/hal/tests/TESTS/mbed_hal/sleep/sleep_test_utils.h + +using namespace std::chrono; + +namespace utils::sleep { + +constexpr inline auto SERIAL_FLUSH_TIME_MS = 150ms; +constexpr inline auto US_PER_S = 1'000'000; +constexpr inline auto DEEPSLEEP_MODE_DELTA_US = uint32_t {10000 + 125 + 5}; + +inline auto ticks_to_us(unsigned int ticks, unsigned int freq) -> unsigned int +{ + return static_cast(static_cast(ticks) * US_PER_S / freq); +} + +inline auto us_to_ticks(unsigned int us, unsigned int freq) -> unsigned int +{ + return static_cast(static_cast(us) * freq / US_PER_S); +} + +inline auto overflow_protect(unsigned int timestamp, unsigned int ticker_width) -> unsigned int +{ + unsigned int counter_mask = ((1 << ticker_width) - 1); + + return (timestamp & counter_mask); +} + +inline auto compare_timestamps(unsigned int delta_ticks, unsigned int ticker_width, unsigned int expected, + unsigned int actual) -> bool +{ + const unsigned int counter_mask = ((1 << ticker_width) - 1); + + const unsigned int lower_bound = ((expected - delta_ticks) & counter_mask); + const unsigned int upper_bound = ((expected + delta_ticks) & counter_mask); + + // NOLINTBEGIN + if (lower_bound < upper_bound) { + if (actual >= lower_bound && actual <= upper_bound) { + return true; + } else { + return false; + } + } else { + if ((actual >= lower_bound && actual <= counter_mask) || (actual >= 0 && actual <= upper_bound)) { + return true; + } else { + return false; + } + } + // NOLINTEND +} + +inline void busy_wait(std::chrono::milliseconds ms) +{ + const auto *info = us_ticker_get_info(); + auto mask = static_cast((1 << info->bits) - 1); + + auto delay = ms.count() * info->frequency / 1000; + auto prev = us_ticker_read(); // NOLINT + + while (delay > 0) { + auto next = us_ticker_read(); // NOLINT + delay -= (next - prev) & mask; + prev = next; + } +} + +inline void us_ticker_isr(const ticker_data_t *const ticker_data) +{ + us_ticker_clear_interrupt(); +} + +#if DEVICE_LPTICKER +inline void lp_ticker_isr(const ticker_data_t *const ticker_data) +{ + lp_ticker_clear_interrupt(); +} +#endif + +} // namespace utils::sleep diff --git a/tests/functional/tests/deep_sleep_log_kit/CMakeLists.txt b/tests/functional/tests/deep_sleep_log_kit/CMakeLists.txt new file mode 100644 index 0000000000..524337f420 --- /dev/null +++ b/tests/functional/tests/deep_sleep_log_kit/CMakeLists.txt @@ -0,0 +1,15 @@ +# Leka - LekaOS +# Copyright 2022 APF France handicap +# SPDX-License-Identifier: Apache-2.0 + +register_functional_test( + TARGET + functional_ut_deep_sleep_log_kit + + INCLUDE_DIRECTORIES + + SOURCES + suite_log_kit.cpp + + LINK_LIBRARIES +) diff --git a/tests/functional/tests/deep_sleep_log_kit/suite_log_kit.cpp b/tests/functional/tests/deep_sleep_log_kit/suite_log_kit.cpp new file mode 100644 index 0000000000..0acc86d2f6 --- /dev/null +++ b/tests/functional/tests/deep_sleep_log_kit/suite_log_kit.cpp @@ -0,0 +1,94 @@ +// Leka - LekaOS +// Copyright 2022 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "tests/config.h" +#include "tests/utils.h" +#include "tests/utils_sleep.h" + +using namespace boost::ut; +using namespace std::chrono; +using namespace boost::ut::bdd; + +// ? tests inspired from https://github.com/ARMmbed/mbed-os +// ? https://github.com/ARMmbed/mbed-os/blob/master/hal/tests/TESTS/mbed_hal/sleep/main.cpp + +suite suite_log_kit = [] { + rtos::ThisThread::sleep_for(2000ms); + + scenario("(default) LogKit w/ input_enable(false)") = [] { + given("I use the default logger") = [] { + log_info("using default logger"); + rtos::ThisThread::sleep_for(500ms); + + then("I expect deep sleep to be possible") = [] { + const ticker_data_t *lp_ticker = get_lp_ticker_data(); + const unsigned int lp_ticker_freq = lp_ticker->interface->get_info()->frequency; + + // ? Give time to test to finish UART transmission before entering deep sleep mode + utils::sleep::busy_wait(utils::sleep::SERIAL_FLUSH_TIME_MS); + + auto can_deep_sleep = sleep_manager_can_deep_sleep(); + expect(can_deep_sleep) << "deep sleep not possible"; + + const timestamp_t wakeup_time = lp_ticker_read() + utils::sleep::us_to_ticks(20000, lp_ticker_freq); + lp_ticker_set_interrupt(wakeup_time); + + auto can_deep_sleep_test_check = sleep_manager_can_deep_sleep_test_check(); + expect(can_deep_sleep_test_check); + }; + }; + }; + + scenario("LogKit w/ input_enable(true)") = [] { + given("I set input_enable(true) and use the default logger") = [] { + leka::logger::internal::enable_filehandle_input(); + log_info("using default logger"); + rtos::ThisThread::sleep_for(500ms); + + then("I expect deep sleep to NOT be possible") = [] { + const ticker_data_t *lp_ticker = get_lp_ticker_data(); + const unsigned int lp_ticker_freq = lp_ticker->interface->get_info()->frequency; + + // ? Give time to test to finish UART transmission before entering deep sleep mode + utils::sleep::busy_wait(utils::sleep::SERIAL_FLUSH_TIME_MS); + + auto can_deep_sleep = sleep_manager_can_deep_sleep(); + expect(not can_deep_sleep) << "deep sleep STILL possible"; + + const timestamp_t wakeup_time = lp_ticker_read() + utils::sleep::us_to_ticks(20000, lp_ticker_freq); + lp_ticker_set_interrupt(wakeup_time); + + auto can_deep_sleep_test_check = sleep_manager_can_deep_sleep_test_check(); + expect(not can_deep_sleep_test_check); + }; + }; + }; + + scenario("LogKit w/ disable_filehandle_input()") = [] { + given("I set input_enable(true) and use the default logger") = [] { + leka::logger::internal::disable_filehandle_input(); + log_info("using default logger"); + rtos::ThisThread::sleep_for(500ms); + + then("I expect deep sleep to NOT be possible") = [] { + const ticker_data_t *lp_ticker = get_lp_ticker_data(); + const unsigned int lp_ticker_freq = lp_ticker->interface->get_info()->frequency; + + // ? Give time to test to finish UART transmission before entering deep sleep mode + utils::sleep::busy_wait(utils::sleep::SERIAL_FLUSH_TIME_MS); + + auto can_deep_sleep = sleep_manager_can_deep_sleep(); + expect(can_deep_sleep) << "deep sleep not possible"; + + const timestamp_t wakeup_time = lp_ticker_read() + utils::sleep::us_to_ticks(20000, lp_ticker_freq); + lp_ticker_set_interrupt(wakeup_time); + + auto can_deep_sleep_test_check = sleep_manager_can_deep_sleep_test_check(); + expect(can_deep_sleep_test_check); + }; + }; + }; +}; diff --git a/tests/functional/tests/deep_sleep_mbed_hal/CMakeLists.txt b/tests/functional/tests/deep_sleep_mbed_hal/CMakeLists.txt new file mode 100644 index 0000000000..1a34dbeca8 --- /dev/null +++ b/tests/functional/tests/deep_sleep_mbed_hal/CMakeLists.txt @@ -0,0 +1,15 @@ +# Leka - LekaOS +# Copyright 2022 APF France handicap +# SPDX-License-Identifier: Apache-2.0 + +register_functional_test( + TARGET + functional_ut_deep_sleep_mbed_hal + + INCLUDE_DIRECTORIES + + SOURCES + suite_mbed_hal.cpp + + LINK_LIBRARIES +) diff --git a/tests/functional/tests/deep_sleep_mbed_hal/suite_mbed_hal.cpp b/tests/functional/tests/deep_sleep_mbed_hal/suite_mbed_hal.cpp new file mode 100644 index 0000000000..1709376fd0 --- /dev/null +++ b/tests/functional/tests/deep_sleep_mbed_hal/suite_mbed_hal.cpp @@ -0,0 +1,161 @@ +// Leka - LekaOS +// Copyright 2022 APF France handicap +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "tests/config.h" +#include "tests/utils.h" +#include "tests/utils_sleep.h" + +using namespace boost::ut; +using namespace std::chrono; +using namespace boost::ut::bdd; + +// ? tests inspired from https://github.com/ARMmbed/mbed-os +// ? https://github.com/ARMmbed/mbed-os/blob/master/hal/tests/TESTS/mbed_hal/sleep/main.cpp + +suite suite_mbed_hal = [] { + // NOLINTBEGIN(bugprone-lambda-function-name) + log_free("\t\033[31m! Important notice\033[0m\n"); + log_free("\tThose tests are very sensitive to timing and system state and they can easily fail\n"); + log_free("\twhen sleep_manager_can_deep_sleep() is called while something is blocking deep sleep\n"); + log_free("\tfor example: ticker, serial write, etc.\n"); + log_free("\tIt might be necessary to run the test multiple time to assess if failure is constant\n"); + log_free("\tor just a glitch in the \033[32mmatrix\033[0m...\n\n"); + // NOLINTEND(bugprone-lambda-function-name) + + rtos::ThisThread::sleep_for(1000ms); + + "high speed clocks - turned off on deep sleep"_test = [] { + const ticker_data_t *us_ticker = get_us_ticker_data(); + const ticker_data_t *lp_ticker = get_lp_ticker_data(); + const unsigned int us_ticker_freq = us_ticker->interface->get_info()->frequency; + const unsigned int lp_ticker_freq = lp_ticker->interface->get_info()->frequency; + const unsigned int us_ticker_width = us_ticker->interface->get_info()->bits; + const unsigned int lp_ticker_width = lp_ticker->interface->get_info()->bits; + const unsigned int us_ticker_mask = ((1 << us_ticker_width) - 1); + + // ? Give time to test to finish UART transmission before entering deep sleep mode + utils::sleep::busy_wait(utils::sleep::SERIAL_FLUSH_TIME_MS); + + auto can_deep_sleep = sleep_manager_can_deep_sleep(); + expect(can_deep_sleep) << "deep sleep not possible"; + + const timestamp_t wakeup_time = lp_ticker_read() + utils::sleep::us_to_ticks(20000, lp_ticker_freq); + lp_ticker_set_interrupt(wakeup_time); + + auto can_deep_sleep_test_check = sleep_manager_can_deep_sleep_test_check(); + expect(can_deep_sleep_test_check); + + auto us_ticks_before_sleep = us_ticker_read(); // NOLINT + + mbed_sleep(); + + auto us_ticks_after_sleep = us_ticker_read(); // NOLINT + auto lp_ticks_after_sleep = lp_ticker_read(); + + // ? High freqency ticker should be disabled in deep-sleep mode. We expect that time difference between + // ? ticker reads before and after the sleep represents only code execution time between calls. + // ? Since we went to sleep for about 20 ms check if time counted by high frequency timer does not + // ? exceed 1 ms. + + auto us_ticks_diff = (us_ticks_before_sleep <= us_ticks_after_sleep) + ? (us_ticks_after_sleep - us_ticks_before_sleep) + : (us_ticker_mask - us_ticks_before_sleep + us_ticks_after_sleep + 1); + + expect(utils::sleep::ticks_to_us(us_ticks_diff, us_ticker_freq) < 1000_u); + + // ? Used for deep-sleep mode, a target should be awake within 10 ms. Define us delta value as follows: + // ? delta = default 10 ms + worst ticker resolution + extra time for code execution + + auto info = "Delta ticks: " + + std::to_string(utils::sleep::us_to_ticks(utils::sleep::DEEPSLEEP_MODE_DELTA_US, lp_ticker_freq)) + + "Ticker width: " + std::to_string(lp_ticker_width) + + ", Expected wake up tick: " + std::to_string(wakeup_time) + + " Actual wake up tick: " + std::to_string(lp_ticks_after_sleep); + + auto timestamps_are_the_same = utils::sleep::compare_timestamps( + utils::sleep::us_to_ticks(utils::sleep::DEEPSLEEP_MODE_DELTA_US, lp_ticker_freq), lp_ticker_width, + wakeup_time, lp_ticks_after_sleep); + + expect(timestamps_are_the_same) << "Delta ticks: " + << utils::sleep::us_to_ticks(utils::sleep::DEEPSLEEP_MODE_DELTA_US, + lp_ticker_freq) + << ", Ticker width: " << lp_ticker_width + << ", Expected wake up tick: " << wakeup_time + << ", Actual wake up tick: " << lp_ticks_after_sleep; + }; + + "lpticker - source of wake-up + 10ms timing"_test = [] { + // ? Test that wake-up time from sleep should be less than 10 ms and + // ? low power ticker interrupt can wake-up target from sleep + + const ticker_data_t *ticker = get_lp_ticker_data(); + const unsigned int ticker_freq = ticker->interface->get_info()->frequency; + const unsigned int ticker_width = ticker->interface->get_info()->bits; + + const ticker_irq_handler_type lp_ticker_irq_handler_org = + set_lp_ticker_irq_handler(utils::sleep::lp_ticker_isr); + + // ? Give time to test to finish UART transmission before entering deep sleep mode + utils::sleep::busy_wait(utils::sleep::SERIAL_FLUSH_TIME_MS); + + auto can_deep_sleep = sleep_manager_can_deep_sleep(); + expect(can_deep_sleep) << "deep sleep not possible"; + + // ? Testing wake-up time 10 ms + for (timestamp_t i = 20'000; i < 200'000; i += 20'000) { + // ? Give time to test to finish UART transmission before entering deep sleep mode + utils::sleep::busy_wait(utils::sleep::SERIAL_FLUSH_TIME_MS); + + // ? Note: lp_ticker_read() operates on ticks + const timestamp_t start_timestamp = lp_ticker_read(); + const timestamp_t next_match_timestamp = utils::sleep::overflow_protect( + start_timestamp + utils::sleep::us_to_ticks(i, ticker_freq), ticker_width); + + lp_ticker_set_interrupt(next_match_timestamp); + + // ? On some targets like STM family boards with LPTIM enabled there is a required delay (~100 us) before + // ? we are able to reprogram LPTIM_COMPARE register back to back. This is handled by the low level lp + // ? ticker wrapper which uses LPTIM_CMPOK interrupt. CMPOK fires when LPTIM_COMPARE register can be + // ? safely reprogrammed again. During this period deep-sleep is locked. This means that on these + // ? platforms we have additional interrupt (CMPOK) fired always ~100 us after programming lp ticker. + // ? Since this interrupt wakes-up the board from the sleep we need to go to sleep after CMPOK is handled. + + auto can_deep_sleep_test_check = sleep_manager_can_deep_sleep_test_check(); + expect(can_deep_sleep_test_check); + + mbed_sleep(); + + // ? On some targets like STM family boards with LPTIM enabled an interrupt is triggered on counter + // ? rollover. We need special handling for cases when next_match_timestamp < start_timestamp (interrupt + // ? is to be fired after rollover). In such case after first wake-up we need to reset interrupt and go + // ? back to sleep waiting for the valid one. NOTE: Above comment (CMPOK) applies also here. +#if MBED_CONF_TARGET_LPTICKER_LPTIM + + if ((next_match_timestamp < start_timestamp) && lp_ticker_read() < next_match_timestamp) { + lp_ticker_set_interrupt(next_match_timestamp); + wait_ns(200000); + mbed_sleep(); + } + +#endif + + const timestamp_t wakeup_timestamp = lp_ticker_read(); + + auto timestamps_are_the_same = utils::sleep::compare_timestamps( + utils::sleep::us_to_ticks(utils::sleep::DEEPSLEEP_MODE_DELTA_US, ticker_freq), ticker_width, + next_match_timestamp, wakeup_timestamp); + + expect(timestamps_are_the_same) + << "Delta ticks: " << utils::sleep::us_to_ticks(utils::sleep::DEEPSLEEP_MODE_DELTA_US, ticker_freq) + << ", Ticker width: " << ticker_width << ", Expected wake up tick: " << next_match_timestamp + << ", Actual wake up tick: " << wakeup_timestamp + << ", delay ticks: " << utils::sleep::us_to_ticks(i, ticker_freq) + << ", wake up after ticks: " << wakeup_timestamp - start_timestamp; + } + + set_lp_ticker_irq_handler(lp_ticker_irq_handler_org); + }; +};