From f2c222be1595d879b16b41defa93f38482dd5ce3 Mon Sep 17 00:00:00 2001 From: Filip Jagodzinski Date: Wed, 18 Jul 2018 16:19:06 +0200 Subject: [PATCH 1/8] Tests: SleepManager: extend test suite New test cases: * "deep sleep lock/unlock" * "deep sleep unbalanced unlock" * "deep sleep locked USHRT_MAX times" * "deep sleep locked more than USHRT_MAX times" * "sleep_auto calls sleep/deep sleep based on lock" --- TESTS/mbed_hal/sleep_manager/main.cpp | 198 ++++++++++++++++-- .../sleep_manager/sleep_manager_api_tests.h | 84 ++++++++ hal/mbed_sleep_manager.c | 8 + 3 files changed, 273 insertions(+), 17 deletions(-) create mode 100644 TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h diff --git a/TESTS/mbed_hal/sleep_manager/main.cpp b/TESTS/mbed_hal/sleep_manager/main.cpp index 3d3e392b50..0717c12f76 100644 --- a/TESTS/mbed_hal/sleep_manager/main.cpp +++ b/TESTS/mbed_hal/sleep_manager/main.cpp @@ -16,46 +16,210 @@ #include "utest/utest.h" #include "unity/unity.h" #include "greentea-client/test_env.h" +#include +#include "mbed.h" +#include "mbed_lp_ticker_wrapper.h" +#include "sleep_manager_api_tests.h" #if !DEVICE_SLEEP #error [NOT_SUPPORTED] test not supported #endif -using namespace utest::v1; +#define SLEEP_DURATION_US 100000ULL +#define SERIAL_FLUSH_TIME_MS 20 -void sleep_manager_deepsleep_counter_test() +using utest::v1::Case; +using utest::v1::Specification; +using utest::v1::Harness; + +static uint32_t num_test_errors = 0UL; + +mbed_error_status_t mbed_error(mbed_error_status_t error_status, const char *error_msg, unsigned int error_value, + const char *filename, int line_number) { - bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); - TEST_ASSERT_TRUE(deep_sleep_allowed); + (void) error_status; + (void) error_msg; + (void) error_value; + (void) filename; + (void) line_number; + + num_test_errors++; + return MBED_SUCCESS; +} + +void test_lock_unlock() +{ + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); sleep_manager_lock_deep_sleep(); - deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); - TEST_ASSERT_FALSE(deep_sleep_allowed); + TEST_ASSERT_FALSE(sleep_manager_can_deep_sleep()); sleep_manager_unlock_deep_sleep(); - deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); - TEST_ASSERT_TRUE(deep_sleep_allowed); + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); } -utest::v1::status_t greentea_failure_handler(const Case *const source, const failure_t reason) +void test_lone_unlock() { - greentea_case_failure_abort_handler(source, reason); - return STATUS_CONTINUE; + uint32_t expected_err_count = num_test_errors + 1; + sleep_manager_unlock_deep_sleep(); + TEST_ASSERT_EQUAL_UINT32(expected_err_count, num_test_errors); + + // Make sure upcoming tests won't be broken. + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); } -utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +void test_lock_eq_ushrt_max() { - GREENTEA_SETUP(20, "default_auto"); - return greentea_test_setup_handler(number_of_cases); + uint32_t lock_count = 0; + while (lock_count < USHRT_MAX) { + sleep_manager_lock_deep_sleep(); + lock_count++; + TEST_ASSERT_FALSE(sleep_manager_can_deep_sleep()); + } + while (lock_count > 1) { + sleep_manager_unlock_deep_sleep(); + lock_count--; + TEST_ASSERT_FALSE(sleep_manager_can_deep_sleep()); + } + sleep_manager_unlock_deep_sleep(); + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); +} + +void test_lock_gt_ushrt_max() +{ + uint32_t lock_count = 0; + while (lock_count < USHRT_MAX) { + sleep_manager_lock_deep_sleep(); + lock_count++; + TEST_ASSERT_FALSE(sleep_manager_can_deep_sleep()); + } + + uint32_t expected_err_count = num_test_errors + 1; + sleep_manager_lock_deep_sleep(); + TEST_ASSERT_EQUAL_UINT32(expected_err_count, num_test_errors); + + // Make sure upcoming tests won't be broken. + while (lock_count > 0) { + sleep_manager_unlock_deep_sleep(); + lock_count--; + } + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); +} + +#if DEVICE_LPTICKER && DEVICE_USTICKER +void wakeup_callback(volatile int *wakeup_flag) +{ + (*wakeup_flag)++; +} + +/* This test is based on the fact that the high-speed clocks are turned off + * in deep sleep mode but remain on in the ordinary sleep mode. Low-speed + * clocks stay on for both sleep and deep sleep modes. + * + * The type of sleep that was actually used by sleep_manager_sleep_auto() + * can be detected by comparing times measured by us and lp tickers. + */ +void test_sleep_auto() +{ + const ticker_data_t *const us_ticker = get_us_ticker_data(); + const ticker_data_t *const lp_ticker = get_lp_ticker_data(); + us_timestamp_t us_ts, lp_ts, us_diff1, us_diff2, lp_diff1, lp_diff2; + LowPowerTimeout lp_timeout; + + sleep_manager_lock_deep_sleep(); + volatile int wakeup_flag = 0; + lp_timeout.attach_us(mbed::callback(wakeup_callback, &wakeup_flag), SLEEP_DURATION_US); + us_ts = ticker_read_us(us_ticker); + lp_ts = ticker_read_us(lp_ticker); + + while (wakeup_flag == 0) { + sleep_manager_sleep_auto(); + } + us_diff1 = ticker_read_us(us_ticker) - us_ts; + lp_diff1 = ticker_read_us(lp_ticker) - lp_ts; + + // Deep sleep locked -- ordinary sleep mode used: + // * us_ticker powered ON, + // * lp_ticker powered ON, + // so both should increment equally. + + // Verify us and lp tickers incremented equally, with 10% tolerance. + TEST_ASSERT_UINT64_WITHIN_MESSAGE( + SLEEP_DURATION_US / 10ULL, lp_diff1, us_diff1, + "Deep sleep mode locked, but still used"); + + sleep_manager_unlock_deep_sleep(); + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); + + // Wait for hardware serial buffers to flush. + wait_ms(SERIAL_FLUSH_TIME_MS); + + wakeup_flag = 0; + lp_timeout.attach_us(mbed::callback(wakeup_callback, &wakeup_flag), SLEEP_DURATION_US); + us_ts = ticker_read_us(us_ticker); + lp_ts = ticker_read_us(lp_ticker); + + while (wakeup_flag == 0) { + sleep_manager_sleep_auto(); + } + us_diff2 = ticker_read_us(us_ticker) - us_ts; + lp_diff2 = ticker_read_us(lp_ticker) - lp_ts; + + // Deep sleep unlocked -- deep sleep mode used: + // * us_ticker powered OFF, + // * lp_ticker powered ON. + // The us_ticker increment represents only the code execution time + // and should be much shorter than both: + // 1. current lp_ticker increment, + // 2. previous us_ticker increment (locked sleep test above) + + // Verify that the current us_ticker increment: + // 1. is at most 10% of lp_ticker increment + // 2. is at most 10% of previous us_ticker increment. + TEST_ASSERT_MESSAGE(us_diff2 < lp_diff2 / 10ULL, "Deep sleep mode unlocked, but not used"); + TEST_ASSERT_MESSAGE(us_diff2 < us_diff1 / 10ULL, "Deep sleep mode unlocked, but not used"); +} +#endif + +utest::v1::status_t testsuite_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(10, "default_auto"); + // Suspend the RTOS kernel scheduler to prevent interference with duration of sleep. + osKernelSuspend(); +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + // Suspend the low power ticker wrapper to prevent interference with deep sleep lock. + lp_ticker_wrapper_suspend(); +#endif +#if DEVICE_LPTICKER && DEVICE_USTICKER + // Make sure HAL tickers are initialized. + us_ticker_init(); + lp_ticker_init(); +#endif + return utest::v1::greentea_test_setup_handler(number_of_cases); +} + +void testsuite_teardown(const size_t passed, const size_t failed, const utest::v1::failure_t failure) +{ +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + lp_ticker_wrapper_resume(); +#endif + osKernelResume(0); + utest::v1::greentea_test_teardown_handler(passed, failed, failure); } Case cases[] = { - Case("sleep manager - deep sleep counter", sleep_manager_deepsleep_counter_test, greentea_failure_handler), + Case("deep sleep lock/unlock", test_lock_unlock), + Case("deep sleep unbalanced unlock", test_lone_unlock), + Case("deep sleep locked USHRT_MAX times", test_lock_eq_ushrt_max), + Case("deep sleep locked more than USHRT_MAX times", test_lock_gt_ushrt_max), +#if DEVICE_LPTICKER && DEVICE_USTICKER + Case("sleep_auto calls sleep/deep sleep based on lock", test_sleep_auto), +#endif }; -Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); +Specification specification(testsuite_setup, cases, testsuite_teardown); int main() { - Harness::run(specification); + return !Harness::run(specification); } diff --git a/TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h b/TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h new file mode 100644 index 0000000000..c99754a084 --- /dev/null +++ b/TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h @@ -0,0 +1,84 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup hal_sleep_manager_tests + * @{ + */ + +#ifndef MBED_HAL_SLEEP_MANAGER_API_TESTS_H +#define MBED_HAL_SLEEP_MANAGER_API_TESTS_H + +#if DEVICE_SLEEP + +/** Test lock/unlock + * + * Given no prior calls to lock/unlock + * When the deep sleep status is checked + * Then the deep sleep is allowed + * + * When the lock function is called + * Then the deep sleep is not allowed + * + * When the unlock function is called + * Then the deep sleep is allowed again + */ +void test_lock_unlock(); + +/** Test an unbalanced unlock call + * + * Given the deep sleep has not been locked + * When the deep sleep mode is unlocked + * Then an mbed_error is raised + */ +void test_lone_unlock(); + +/** Test lock USHRT_MAX times + * + * Given a device with sleep mode support + * When deep sleep mode is locked USHRT_MAX times + * Then the deep sleep mode is locked + * + * When unlock is called repeatedly + * Then deep sleep mode stays locked until the number + * of unlock calls is equal to number of lock calls + */ +void test_lock_eq_ushrt_max(); + +/** Test lock more than USHRT_MAX times + * + * Given the deep sleep has already been locked USHRT_MAX times + * When the deep sleep mode is locked again + * Then an mbed_error is raised + */ +void test_lock_gt_ushrt_max(); + +/** Test sleep_auto calls sleep and deep sleep based on lock + * + * Given a device with sleep mode support + * When the deep sleep mode is locked + * Then sleep_auto uses sleep mode + * + * When the deep sleep mode is unlocked + * Then sleep_auto uses deep sleep mode + */ +void test_sleep_auto(); + +#endif + +#endif + +/** @}*/ diff --git a/hal/mbed_sleep_manager.c b/hal/mbed_sleep_manager.c index 03f678e826..e0ce00c216 100644 --- a/hal/mbed_sleep_manager.c +++ b/hal/mbed_sleep_manager.c @@ -163,6 +163,10 @@ void sleep_manager_lock_deep_sleep_internal(void) if (deep_sleep_lock == USHRT_MAX) { core_util_critical_section_exit(); MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_HAL, MBED_ERROR_CODE_OVERFLOW), "DeepSleepLock overflow (> USHRT_MAX)", deep_sleep_lock); + // When running sleep_manager tests, the mbed_error() is overridden + // and no longer calls mbed_halt_system(). Return to prevent + // execution of the following code. + return; } core_util_atomic_incr_u16(&deep_sleep_lock, 1); core_util_critical_section_exit(); @@ -174,6 +178,10 @@ void sleep_manager_unlock_deep_sleep_internal(void) if (deep_sleep_lock == 0) { core_util_critical_section_exit(); MBED_ERROR1(MBED_MAKE_ERROR(MBED_MODULE_HAL, MBED_ERROR_CODE_UNDERFLOW), "DeepSleepLock underflow (< 0)", deep_sleep_lock); + // When running sleep_manager tests, the mbed_error() is overridden + // and no longer calls mbed_halt_system(). Return to prevent + // execution of the following code. + return; } core_util_atomic_decr_u16(&deep_sleep_lock, 1); core_util_critical_section_exit(); From 43c22e4977bf3f8b89187822ddf0da101184061f Mon Sep 17 00:00:00 2001 From: Filip Jagodzinski Date: Fri, 20 Jul 2018 12:37:59 +0200 Subject: [PATCH 2/8] Tests: SleepManager: update concurrent test docs --- .../sleep_manager_racecondition/main.cpp | 7 +-- .../sleep_manager_api_racecondition_tests.h | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 TESTS/mbed_hal/sleep_manager_racecondition/sleep_manager_api_racecondition_tests.h diff --git a/TESTS/mbed_hal/sleep_manager_racecondition/main.cpp b/TESTS/mbed_hal/sleep_manager_racecondition/main.cpp index d43492c052..f5e676deeb 100644 --- a/TESTS/mbed_hal/sleep_manager_racecondition/main.cpp +++ b/TESTS/mbed_hal/sleep_manager_racecondition/main.cpp @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #include "utest/utest.h" #include "unity/unity.h" #include "greentea-client/test_env.h" +#include "mbed.h" +#include "sleep_manager_api_racecondition_tests.h" #if !DEVICE_SLEEP #error [NOT_SUPPORTED] test not supported @@ -94,8 +95,8 @@ utest::v1::status_t greentea_test_setup(const size_t number_of_cases) } Case cases[] = { - Case("sleep manager HAL - locking multithreaded", sleep_manager_multithread_test), - Case("sleep manager HAL - locking IRQ", sleep_manager_irq_test), + Case("deep sleep lock/unlock is thread safe", sleep_manager_multithread_test), + Case("deep sleep lock/unlock is IRQ safe", sleep_manager_irq_test), }; Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); diff --git a/TESTS/mbed_hal/sleep_manager_racecondition/sleep_manager_api_racecondition_tests.h b/TESTS/mbed_hal/sleep_manager_racecondition/sleep_manager_api_racecondition_tests.h new file mode 100644 index 0000000000..343b374e99 --- /dev/null +++ b/TESTS/mbed_hal/sleep_manager_racecondition/sleep_manager_api_racecondition_tests.h @@ -0,0 +1,43 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup hal_sleep_manager_tests + * @{ + */ + +#ifndef MBED_HAL_SLEEP_MANAGER_API_RACECONDITION_TESTS_H +#define MBED_HAL_SLEEP_MANAGER_API_RACECONDITION_TESTS_H + +/** Test lock/unlock is thread safe + * + * Given a device with sleep mode support + * When multiple threads are using the sleep manager API + * Then lock/unlock calls are thread safe + */ +void sleep_manager_multithread_test(); + +/** Test lock/unlock is IRQ safe + * + * Given a device with sleep mode support + * When the sleep manager API is used from IRQ and the main thread concurrently + * Then lock/unlock calls are IRQ safe + */ +void sleep_manager_irq_test(); + +#endif + +/** @}*/ From 9dce8b3ab5e3b1f0542e893e9a9db78bdb3baf30 Mon Sep 17 00:00:00 2001 From: Filip Jagodzinski Date: Mon, 23 Jul 2018 15:00:53 +0200 Subject: [PATCH 3/8] HAL: SleepManager: Update header file Move sleep manager API into its own doxygen group. Add defined behavior section and links to tests. --- platform/mbed_power_mgmt.h | 46 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/platform/mbed_power_mgmt.h b/platform/mbed_power_mgmt.h index a3e701af96..c326c452f0 100644 --- a/platform/mbed_power_mgmt.h +++ b/platform/mbed_power_mgmt.h @@ -32,7 +32,8 @@ extern "C" { #endif -/** Sleep manager API +/** + * @defgroup hal_sleep_manager Sleep manager API * The sleep manager provides API to automatically select sleep mode. * * There are two sleep modes: @@ -43,6 +44,17 @@ extern "C" { * are not allowed (=disabled) during the deepsleep. For instance, high frequency * clocks. * + * # Defined behavior + * * The lock is a counter + * * The lock can be locked up to USHRT_MAX - Verified by ::test_lock_eq_ushrt_max and ::test_lock_gt_ushrt_max + * * The lock has to be equally unlocked as locked - Verified by ::test_lone_unlock and ::test_lock_eq_ushrt_max + * * The function sleep_manager_lock_deep_sleep_internal() locks the automatic deep mode selection - Verified by ::test_lock_unlock + * * The function sleep_manager_unlock_deep_sleep_internal() unlocks the automatic deep mode selection - Verified by ::test_lock_unlock + * * The function sleep_manager_sleep_auto() chooses the sleep or deep sleep modes based on the lock - Verified by ::test_sleep_auto + * * The function sleep_manager_lock_deep_sleep_internal() is IRQ and thread safe - Verified by ::sleep_manager_multithread_test and ::sleep_manager_irq_test + * * The function sleep_manager_unlock_deep_sleep_internal() is IRQ and thread safe - Verified by ::sleep_manager_multithread_test and ::sleep_manager_irq_test + * * The function sleep_manager_sleep_auto() is IRQ and thread safe + * * Example: * @code * @@ -63,7 +75,19 @@ extern "C" { * return _sensor.start(event, callback); * } * @endcode + * @{ */ + +/** + * @defgroup hal_sleep_manager_tests Sleep manager API tests + * Tests to validate the proper implementation of the sleep manager + * + * To run the sleep manager hal tests use the command: + * + * mbed test -t -m -n tests-mbed_hal-sleep_manager* + * + */ + #ifdef MBED_SLEEP_TRACING_ENABLED void sleep_tracker_lock(const char *const filename, int line); @@ -203,15 +227,6 @@ static inline void deepsleep(void) #endif /* DEVICE_SLEEP */ } -/** Resets the processor and most of the sub-system - * - * @note Does not affect the debug sub-system - */ -static inline void system_reset(void) -{ - NVIC_SystemReset(); -} - /** Provides the time spent in sleep mode since boot. * * @return Time spent in sleep @@ -240,6 +255,17 @@ us_timestamp_t mbed_time_idle(void); */ us_timestamp_t mbed_uptime(void); +/** @}*/ + +/** Resets the processor and most of the sub-system + * + * @note Does not affect the debug sub-system + */ +static inline void system_reset(void) +{ + NVIC_SystemReset(); +} + #ifdef __cplusplus } #endif From 83745b99c5dff9f78223b60712ed93b347dcc86b Mon Sep 17 00:00:00 2001 From: Filip Jagodzinski Date: Tue, 28 Aug 2018 15:36:40 +0200 Subject: [PATCH 4/8] Tests: SleepManager: Add test for new check() fun sleep_manager_can_deep_sleep_test_check() is intended to be used in test code. --- TESTS/mbed_hal/sleep_manager/main.cpp | 56 ++++++++++++++++++- .../sleep_manager/sleep_manager_api_tests.h | 18 ++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/TESTS/mbed_hal/sleep_manager/main.cpp b/TESTS/mbed_hal/sleep_manager/main.cpp index 0717c12f76..0a152e22bc 100644 --- a/TESTS/mbed_hal/sleep_manager/main.cpp +++ b/TESTS/mbed_hal/sleep_manager/main.cpp @@ -27,6 +27,8 @@ #define SLEEP_DURATION_US 100000ULL #define SERIAL_FLUSH_TIME_MS 20 +#define DEEP_SLEEP_TEST_CHECK_WAIT_US 2000 +#define DEEP_SLEEP_TEST_CHECK_WAIT_DELTA_US 200 using utest::v1::Case; using utest::v1::Specification; @@ -106,7 +108,8 @@ void test_lock_gt_ushrt_max() TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); } -#if DEVICE_LPTICKER && DEVICE_USTICKER +#if DEVICE_LPTICKER +#if DEVICE_USTICKER void wakeup_callback(volatile int *wakeup_flag) { (*wakeup_flag)++; @@ -181,6 +184,52 @@ void test_sleep_auto() } #endif +void test_lock_unlock_test_check() +{ + // Use LowPowerTimer instead of Timer to prevent deep sleep lock. + LowPowerTimer lp_timer; + us_timestamp_t exec_time_unlocked, exec_time_locked; + LowPowerTimeout lp_timeout; + + // Deep sleep unlocked: + // * sleep_manager_can_deep_sleep() returns true, + // * sleep_manager_can_deep_sleep_test_check() returns true instantly. + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); + lp_timer.start(); + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep_test_check()); + lp_timer.stop(); + exec_time_unlocked = lp_timer.read_high_resolution_us(); + + // Deep sleep locked: + // * sleep_manager_can_deep_sleep() returns false, + // * sleep_manager_can_deep_sleep_test_check() returns false with 2 ms delay. + sleep_manager_lock_deep_sleep(); + TEST_ASSERT_FALSE(sleep_manager_can_deep_sleep()); + lp_timer.reset(); + lp_timer.start(); + TEST_ASSERT_FALSE(sleep_manager_can_deep_sleep_test_check()); + lp_timer.stop(); + exec_time_locked = lp_timer.read_high_resolution_us(); + TEST_ASSERT_UINT64_WITHIN(DEEP_SLEEP_TEST_CHECK_WAIT_DELTA_US, DEEP_SLEEP_TEST_CHECK_WAIT_US, + exec_time_locked - exec_time_unlocked); + + // Deep sleep unlocked with a 1 ms delay: + // * sleep_manager_can_deep_sleep() returns false, + // * sleep_manager_can_deep_sleep_test_check() returns true with a 1 ms delay, + // * sleep_manager_can_deep_sleep() returns true when checked again. + lp_timer.reset(); + lp_timeout.attach_us(mbed::callback(sleep_manager_unlock_deep_sleep_internal), + DEEP_SLEEP_TEST_CHECK_WAIT_US / 2); + lp_timer.start(); + TEST_ASSERT_FALSE(sleep_manager_can_deep_sleep()); + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep_test_check()); + lp_timer.stop(); + TEST_ASSERT_UINT64_WITHIN(DEEP_SLEEP_TEST_CHECK_WAIT_DELTA_US, DEEP_SLEEP_TEST_CHECK_WAIT_US / 2, + lp_timer.read_high_resolution_us()); + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); +} +#endif + utest::v1::status_t testsuite_setup(const size_t number_of_cases) { GREENTEA_SETUP(10, "default_auto"); @@ -212,8 +261,11 @@ Case cases[] = { Case("deep sleep unbalanced unlock", test_lone_unlock), Case("deep sleep locked USHRT_MAX times", test_lock_eq_ushrt_max), Case("deep sleep locked more than USHRT_MAX times", test_lock_gt_ushrt_max), -#if DEVICE_LPTICKER && DEVICE_USTICKER +#if DEVICE_LPTICKER +#if DEVICE_USTICKER Case("sleep_auto calls sleep/deep sleep based on lock", test_sleep_auto), +#endif + Case("deep sleep lock/unlock test_check", test_lock_unlock_test_check), #endif }; diff --git a/TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h b/TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h index c99754a084..5a41780379 100644 --- a/TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h +++ b/TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h @@ -77,6 +77,24 @@ void test_lock_gt_ushrt_max(); */ void test_sleep_auto(); +/** Test lock/unlock test_check fun + * + * Given the deep sleep has not been locked + * When the deep sleep status is checked + * Then sleep_manager_can_deep_sleep() returns true + * and sleep_manager_can_deep_sleep_test_check() returns true instantly. + * + * When the deep sleep mode is locked + * Then sleep_manager_can_deep_sleep() returns false + * and sleep_manager_can_deep_sleep_test_check() returns false with 2 ms delay. + * + * When the deep sleep mode is unlocked with a 1 ms delay + * Then sleep_manager_can_deep_sleep() returns false + * and sleep_manager_can_deep_sleep_test_check() returns true with 1 ms delay + * and sleep_manager_can_deep_sleep() returns true when checked again. + */ +void test_lock_unlock_test_check(); + #endif #endif From 17e29618e665263a9d5ae7bcde0994df95523525 Mon Sep 17 00:00:00 2001 From: Filip Jagodzinski Date: Thu, 30 Aug 2018 12:29:35 +0200 Subject: [PATCH 5/8] Tests: SleepManager: Fix test suite setup Use ticker init functions from upper HAL layer to allow proper handling of ticker overflows. --- TESTS/mbed_hal/sleep_manager/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TESTS/mbed_hal/sleep_manager/main.cpp b/TESTS/mbed_hal/sleep_manager/main.cpp index 0a152e22bc..7157f10721 100644 --- a/TESTS/mbed_hal/sleep_manager/main.cpp +++ b/TESTS/mbed_hal/sleep_manager/main.cpp @@ -241,8 +241,8 @@ utest::v1::status_t testsuite_setup(const size_t number_of_cases) #endif #if DEVICE_LPTICKER && DEVICE_USTICKER // Make sure HAL tickers are initialized. - us_ticker_init(); - lp_ticker_init(); + ticker_read(get_us_ticker_data()); + ticker_read(get_lp_ticker_data()); #endif return utest::v1::greentea_test_setup_handler(number_of_cases); } From f415af814750b8e19342916f145811643759f163 Mon Sep 17 00:00:00 2001 From: Filip Jagodzinski Date: Mon, 24 Sep 2018 14:07:15 +0200 Subject: [PATCH 6/8] Tests: SleepManager: Update test_check() test Increase delta to 500 us. This value is still short enough to detect incorrect behavior of the sleep_manager_can_deep_sleep_test_check() fun, but allows the targets with low LP timer accuracy to pass, i.e. NUCLEO_F429ZI. --- TESTS/mbed_hal/sleep_manager/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TESTS/mbed_hal/sleep_manager/main.cpp b/TESTS/mbed_hal/sleep_manager/main.cpp index 7157f10721..05f7e36f51 100644 --- a/TESTS/mbed_hal/sleep_manager/main.cpp +++ b/TESTS/mbed_hal/sleep_manager/main.cpp @@ -28,7 +28,7 @@ #define SLEEP_DURATION_US 100000ULL #define SERIAL_FLUSH_TIME_MS 20 #define DEEP_SLEEP_TEST_CHECK_WAIT_US 2000 -#define DEEP_SLEEP_TEST_CHECK_WAIT_DELTA_US 200 +#define DEEP_SLEEP_TEST_CHECK_WAIT_DELTA_US 500 using utest::v1::Case; using utest::v1::Specification; From 9e34f76b4e0fe2b2311eb7630d7088bbe317dbda Mon Sep 17 00:00:00 2001 From: Filip Jagodzinski Date: Tue, 9 Oct 2018 13:50:03 +0200 Subject: [PATCH 7/8] Tests: Sleep: Extract test utility functions Move a few of utility functions so other tests can use them. --- TESTS/mbed_hal/sleep/main.cpp | 84 +---------------- TESTS/mbed_hal/sleep/sleep_test_utils.h | 114 ++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 83 deletions(-) create mode 100644 TESTS/mbed_hal/sleep/sleep_test_utils.h diff --git a/TESTS/mbed_hal/sleep/main.cpp b/TESTS/mbed_hal/sleep/main.cpp index 4d98274278..04f4d3e744 100644 --- a/TESTS/mbed_hal/sleep/main.cpp +++ b/TESTS/mbed_hal/sleep/main.cpp @@ -25,26 +25,9 @@ #include "greentea-client/test_env.h" #include "mbed_lp_ticker_wrapper.h" +#include "sleep_test_utils.h" #include "sleep_api_tests.h" -#define US_PER_S 1000000 - -/* Flush serial buffer before deep sleep - * - * Since deepsleep() may shut down the UART peripheral, we wait for some time - * to allow for hardware serial buffers to completely flush. - * - * Take NUMAKER_PFM_NUC472 as an example: - * Its UART peripheral has 16-byte Tx FIFO. With baud rate set to 9600, flush - * Tx FIFO would take: 16 * 8 * 1000 / 9600 = 13.3 (ms). So set wait time to - * 20ms here for safe. - * - * This should be replaced with a better function that checks if the - * hardware buffers are empty. However, such an API does not exist now, - * so we'll use the busy_wait_ms() function for now. - */ -#define SERIAL_FLUSH_TIME_MS 20 - using namespace utest::v1; static char info[512] = {0}; @@ -67,71 +50,6 @@ static const uint32_t sleep_mode_delta_us = (10 + 4 + 5); * delta = default 10 ms + worst ticker resolution + extra time for code execution */ static const uint32_t deepsleep_mode_delta_us = (10000 + 125 + 5); -unsigned int ticks_to_us(unsigned int ticks, unsigned int freq) -{ - return (unsigned int)((unsigned long long) ticks * US_PER_S / freq); -} - -unsigned int us_to_ticks(unsigned int us, unsigned int freq) -{ - return (unsigned int)((unsigned long long) us * freq / US_PER_S); -} - -unsigned int overflow_protect(unsigned int timestamp, unsigned int ticker_width) -{ - unsigned int counter_mask = ((1 << ticker_width) - 1); - - return (timestamp & counter_mask); -} - -bool compare_timestamps(unsigned int delta_ticks, unsigned int ticker_width, unsigned int expected, unsigned int actual) -{ - 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); - - 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; - } - } -} - -void busy_wait_ms(int ms) -{ - const ticker_info_t *info = us_ticker_get_info(); - uint32_t mask = (1 << info->bits) - 1; - int delay = (int)((uint64_t)ms * info->frequency / 1000); - - uint32_t prev = us_ticker_read(); - while (delay > 0) { - uint32_t next = us_ticker_read(); - delay -= (next - prev) & mask; - prev = next; - } -} - -void us_ticker_isr(const ticker_data_t *const ticker_data) -{ - us_ticker_clear_interrupt(); -} - -#ifdef DEVICE_LPTICKER -void lp_ticker_isr(const ticker_data_t *const ticker_data) -{ - lp_ticker_clear_interrupt(); -} -#endif - /* Test that wake-up time from sleep should be less than 10 us and * high frequency ticker interrupt can wake-up target from sleep. */ void sleep_usticker_test() diff --git a/TESTS/mbed_hal/sleep/sleep_test_utils.h b/TESTS/mbed_hal/sleep/sleep_test_utils.h new file mode 100644 index 0000000000..bf5a522d03 --- /dev/null +++ b/TESTS/mbed_hal/sleep/sleep_test_utils.h @@ -0,0 +1,114 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @addtogroup hal_sleep_test_utils + * @{ + */ + +#ifndef MBED_SLEEP_TEST_UTILS_H +#define MBED_SLEEP_TEST_UTILS_H + +#include "hal/ticker_api.h" +#include "hal/us_ticker_api.h" +#include "hal/lp_ticker_api.h" + +/* Flush serial buffer before deep sleep + * + * Since deepsleep() may shut down the UART peripheral, we wait for some time + * to allow for hardware serial buffers to completely flush. + * + * Take NUMAKER_PFM_NUC472 as an example: + * Its UART peripheral has 16-byte Tx FIFO. With baud rate set to 9600, flush + * Tx FIFO would take: 16 * 8 * 1000 / 9600 = 13.3 (ms). So set wait time to + * 20ms here for safe. + * + * This should be replaced with a better function that checks if the + * hardware buffers are empty. However, such an API does not exist now, + * so we'll use the busy_wait_ms() function for now. + */ +#define SERIAL_FLUSH_TIME_MS 20 + +#define US_PER_S 1000000 + +unsigned int ticks_to_us(unsigned int ticks, unsigned int freq) +{ + return (unsigned int) ((unsigned long long) ticks * US_PER_S / freq); +} + +unsigned int us_to_ticks(unsigned int us, unsigned int freq) +{ + return (unsigned int) ((unsigned long long) us * freq / US_PER_S); +} + +unsigned int overflow_protect(unsigned int timestamp, unsigned int ticker_width) +{ + unsigned int counter_mask = ((1 << ticker_width) - 1); + + return (timestamp & counter_mask); +} + +bool compare_timestamps(unsigned int delta_ticks, unsigned int ticker_width, unsigned int expected, unsigned int actual) +{ + 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); + + 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; + } + } +} + +void busy_wait_ms(int ms) +{ + const ticker_info_t *info = us_ticker_get_info(); + uint32_t mask = (1 << info->bits) - 1; + int delay = (int) ((uint64_t) ms * info->frequency / 1000); + + uint32_t prev = us_ticker_read(); + while (delay > 0) { + uint32_t next = us_ticker_read(); + delay -= (next - prev) & mask; + prev = next; + } +} + +void us_ticker_isr(const ticker_data_t * const ticker_data) +{ + us_ticker_clear_interrupt(); +} + +#ifdef DEVICE_LPTICKER +void lp_ticker_isr(const ticker_data_t * const ticker_data) +{ + lp_ticker_clear_interrupt(); +} +#endif + +#endif + +/** @}*/ From 3fcd8c4a3bba79f2f7d0b05de3b2b5022e02ccfd Mon Sep 17 00:00:00 2001 From: Filip Jagodzinski Date: Tue, 9 Oct 2018 17:15:51 +0200 Subject: [PATCH 8/8] Tests: SleepManager: Fix sleep_auto test Use us & lp tickers directly, without the common ticker layer. --- TESTS/mbed_hal/sleep_manager/main.cpp | 122 +++++++++++++++----------- 1 file changed, 73 insertions(+), 49 deletions(-) diff --git a/TESTS/mbed_hal/sleep_manager/main.cpp b/TESTS/mbed_hal/sleep_manager/main.cpp index 05f7e36f51..e33172769a 100644 --- a/TESTS/mbed_hal/sleep_manager/main.cpp +++ b/TESTS/mbed_hal/sleep_manager/main.cpp @@ -19,14 +19,14 @@ #include #include "mbed.h" #include "mbed_lp_ticker_wrapper.h" +#include "../sleep/sleep_test_utils.h" #include "sleep_manager_api_tests.h" #if !DEVICE_SLEEP #error [NOT_SUPPORTED] test not supported #endif -#define SLEEP_DURATION_US 100000ULL -#define SERIAL_FLUSH_TIME_MS 20 +#define SLEEP_DURATION_US 20000ULL #define DEEP_SLEEP_TEST_CHECK_WAIT_US 2000 #define DEEP_SLEEP_TEST_CHECK_WAIT_DELTA_US 500 @@ -110,9 +110,38 @@ void test_lock_gt_ushrt_max() #if DEVICE_LPTICKER #if DEVICE_USTICKER -void wakeup_callback(volatile int *wakeup_flag) +utest::v1::status_t testcase_setup(const Case * const source, const size_t index_of_case) { - (*wakeup_flag)++; + // Suspend the RTOS kernel scheduler to prevent interference with duration of sleep. + osKernelSuspend(); +#if DEVICE_LPTICKER + ticker_suspend(get_lp_ticker_data()); +#if (LPTICKER_DELAY_TICKS > 0) + // Suspend the low power ticker wrapper to prevent interference with deep sleep lock. + lp_ticker_wrapper_suspend(); +#endif +#endif + ticker_suspend(get_us_ticker_data()); + // Make sure HAL tickers are initialized. + us_ticker_init(); +#if DEVICE_LPTICKER + lp_ticker_init(); +#endif + return utest::v1::greentea_case_setup_handler(source, index_of_case); +} + +utest::v1::status_t testcase_teardown(const Case * const source, const size_t passed, const size_t failed, + const utest::v1::failure_t failure) +{ + ticker_resume(get_us_ticker_data()); +#if DEVICE_LPTICKER +#if (LPTICKER_DELAY_TICKS > 0) + lp_ticker_wrapper_resume(); +#endif + ticker_resume(get_lp_ticker_data()); +#endif + osKernelResume(0); + return utest::v1::greentea_case_teardown_handler(source, passed, failed, failure); } /* This test is based on the fact that the high-speed clocks are turned off @@ -124,22 +153,26 @@ void wakeup_callback(volatile int *wakeup_flag) */ void test_sleep_auto() { - const ticker_data_t *const us_ticker = get_us_ticker_data(); - const ticker_data_t *const lp_ticker = get_lp_ticker_data(); - us_timestamp_t us_ts, lp_ts, us_diff1, us_diff2, lp_diff1, lp_diff2; - LowPowerTimeout lp_timeout; + const ticker_info_t *us_ticker_info = get_us_ticker_data()->interface->get_info(); + const unsigned us_ticker_mask = ((1 << us_ticker_info->bits) - 1); + const ticker_irq_handler_type us_ticker_irq_handler_org = set_us_ticker_irq_handler(us_ticker_isr); + const ticker_info_t *lp_ticker_info = get_lp_ticker_data()->interface->get_info(); + const unsigned lp_ticker_mask = ((1 << lp_ticker_info->bits) - 1); + const ticker_irq_handler_type lp_ticker_irq_handler_org = set_lp_ticker_irq_handler(lp_ticker_isr); + us_timestamp_t us_ts1, us_ts2, lp_ts1, lp_ts2, us_diff1, us_diff2, lp_diff1, lp_diff2; sleep_manager_lock_deep_sleep(); - volatile int wakeup_flag = 0; - lp_timeout.attach_us(mbed::callback(wakeup_callback, &wakeup_flag), SLEEP_DURATION_US); - us_ts = ticker_read_us(us_ticker); - lp_ts = ticker_read_us(lp_ticker); + uint32_t lp_wakeup_ts_raw = lp_ticker_read() + us_to_ticks(SLEEP_DURATION_US, lp_ticker_info->frequency); + timestamp_t lp_wakeup_ts = overflow_protect(lp_wakeup_ts_raw, lp_ticker_info->bits); + lp_ticker_set_interrupt(lp_wakeup_ts); + us_ts1 = ticks_to_us(us_ticker_read(), us_ticker_info->frequency); + lp_ts1 = ticks_to_us(lp_ticker_read(), lp_ticker_info->frequency); - while (wakeup_flag == 0) { - sleep_manager_sleep_auto(); - } - us_diff1 = ticker_read_us(us_ticker) - us_ts; - lp_diff1 = ticker_read_us(lp_ticker) - lp_ts; + sleep_manager_sleep_auto(); + us_ts2 = ticks_to_us(us_ticker_read(), us_ticker_info->frequency); + us_diff1 = (us_ts1 <= us_ts2) ? (us_ts2 - us_ts1) : (us_ticker_mask - us_ts1 + us_ts2 + 1); + lp_ts2 = ticks_to_us(lp_ticker_read(), lp_ticker_info->frequency); + lp_diff1 = (lp_ts1 <= lp_ts2) ? (lp_ts2 - lp_ts1) : (lp_ticker_mask - lp_ts1 + lp_ts2 + 1); // Deep sleep locked -- ordinary sleep mode used: // * us_ticker powered ON, @@ -155,18 +188,19 @@ void test_sleep_auto() TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); // Wait for hardware serial buffers to flush. - wait_ms(SERIAL_FLUSH_TIME_MS); + busy_wait_ms(SERIAL_FLUSH_TIME_MS); - wakeup_flag = 0; - lp_timeout.attach_us(mbed::callback(wakeup_callback, &wakeup_flag), SLEEP_DURATION_US); - us_ts = ticker_read_us(us_ticker); - lp_ts = ticker_read_us(lp_ticker); + lp_wakeup_ts_raw = lp_ticker_read() + us_to_ticks(SLEEP_DURATION_US, lp_ticker_info->frequency); + lp_wakeup_ts = overflow_protect(lp_wakeup_ts_raw, lp_ticker_info->bits); + lp_ticker_set_interrupt(lp_wakeup_ts); + us_ts1 = ticks_to_us(us_ticker_read(), us_ticker_info->frequency); + lp_ts1 = ticks_to_us(lp_ticker_read(), lp_ticker_info->frequency); - while (wakeup_flag == 0) { - sleep_manager_sleep_auto(); - } - us_diff2 = ticker_read_us(us_ticker) - us_ts; - lp_diff2 = ticker_read_us(lp_ticker) - lp_ts; + sleep_manager_sleep_auto(); + us_ts2 = ticks_to_us(us_ticker_read(), us_ticker_info->frequency); + us_diff2 = (us_ts1 <= us_ts2) ? (us_ts2 - us_ts1) : (us_ticker_mask - us_ts1 + us_ts2 + 1); + lp_ts2 = ticks_to_us(lp_ticker_read(), lp_ticker_info->frequency); + lp_diff2 = (lp_ts1 <= lp_ts2) ? (lp_ts2 - lp_ts1) : (lp_ticker_mask - lp_ts1 + lp_ts2 + 1); // Deep sleep unlocked -- deep sleep mode used: // * us_ticker powered OFF, @@ -181,11 +215,18 @@ void test_sleep_auto() // 2. is at most 10% of previous us_ticker increment. TEST_ASSERT_MESSAGE(us_diff2 < lp_diff2 / 10ULL, "Deep sleep mode unlocked, but not used"); TEST_ASSERT_MESSAGE(us_diff2 < us_diff1 / 10ULL, "Deep sleep mode unlocked, but not used"); + + set_us_ticker_irq_handler(us_ticker_irq_handler_org); + set_lp_ticker_irq_handler(lp_ticker_irq_handler_org); } #endif void test_lock_unlock_test_check() { + // Make sure HAL tickers are initialized. + ticker_read(get_us_ticker_data()); + ticker_read(get_lp_ticker_data()); + // Use LowPowerTimer instead of Timer to prevent deep sleep lock. LowPowerTimer lp_timer; us_timestamp_t exec_time_unlocked, exec_time_locked; @@ -233,29 +274,9 @@ void test_lock_unlock_test_check() utest::v1::status_t testsuite_setup(const size_t number_of_cases) { GREENTEA_SETUP(10, "default_auto"); - // Suspend the RTOS kernel scheduler to prevent interference with duration of sleep. - osKernelSuspend(); -#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) - // Suspend the low power ticker wrapper to prevent interference with deep sleep lock. - lp_ticker_wrapper_suspend(); -#endif -#if DEVICE_LPTICKER && DEVICE_USTICKER - // Make sure HAL tickers are initialized. - ticker_read(get_us_ticker_data()); - ticker_read(get_lp_ticker_data()); -#endif return utest::v1::greentea_test_setup_handler(number_of_cases); } -void testsuite_teardown(const size_t passed, const size_t failed, const utest::v1::failure_t failure) -{ -#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) - lp_ticker_wrapper_resume(); -#endif - osKernelResume(0); - utest::v1::greentea_test_teardown_handler(passed, failed, failure); -} - Case cases[] = { Case("deep sleep lock/unlock", test_lock_unlock), Case("deep sleep unbalanced unlock", test_lone_unlock), @@ -263,13 +284,16 @@ Case cases[] = { Case("deep sleep locked more than USHRT_MAX times", test_lock_gt_ushrt_max), #if DEVICE_LPTICKER #if DEVICE_USTICKER - Case("sleep_auto calls sleep/deep sleep based on lock", test_sleep_auto), + Case("sleep_auto calls sleep/deep sleep based on lock", + (utest::v1::case_setup_handler_t) testcase_setup, + test_sleep_auto, + (utest::v1::case_teardown_handler_t) testcase_teardown), #endif Case("deep sleep lock/unlock test_check", test_lock_unlock_test_check), #endif }; -Specification specification(testsuite_setup, cases, testsuite_teardown); +Specification specification(testsuite_setup, cases); int main() {