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 + +/** @}*/ diff --git a/TESTS/mbed_hal/sleep_manager/main.cpp b/TESTS/mbed_hal/sleep_manager/main.cpp index 3d3e392b50..e33172769a 100644 --- a/TESTS/mbed_hal/sleep_manager/main.cpp +++ b/TESTS/mbed_hal/sleep_manager/main.cpp @@ -16,46 +16,286 @@ #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/sleep_test_utils.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 20000ULL +#define DEEP_SLEEP_TEST_CHECK_WAIT_US 2000 +#define DEEP_SLEEP_TEST_CHECK_WAIT_DELTA_US 500 -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 +#if DEVICE_USTICKER +utest::v1::status_t testcase_setup(const Case * const source, const size_t index_of_case) +{ + // 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 + * 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_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(); + 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); + + 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, + // * 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. + busy_wait_ms(SERIAL_FLUSH_TIME_MS); + + 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); + + 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, + // * 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"); + + 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; + 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"); + return utest::v1::greentea_test_setup_handler(number_of_cases); } 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 +#if DEVICE_USTICKER + 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(greentea_test_setup, cases, greentea_test_teardown_handler); +Specification specification(testsuite_setup, cases); 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..5a41780379 --- /dev/null +++ b/TESTS/mbed_hal/sleep_manager/sleep_manager_api_tests.h @@ -0,0 +1,102 @@ +/* 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(); + +/** 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 + +/** @}*/ 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 + +/** @}*/ 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(); 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