From 23e6d50000b3138c7b50781d5deabb65306d53dd Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Fri, 13 Jul 2018 13:58:37 -0500 Subject: [PATCH 1/7] Add ticker suspend/resume API Add an API to suspend and resume the ticker. --- hal/mbed_ticker_api.c | 39 ++++++++++++++++++++++++++++++++++++++- hal/ticker_api.h | 22 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/hal/mbed_ticker_api.c b/hal/mbed_ticker_api.c index f1dcf3bf107..cdddc507fd6 100644 --- a/hal/mbed_ticker_api.c +++ b/hal/mbed_ticker_api.c @@ -32,6 +32,9 @@ static void initialize(const ticker_data_t *ticker) if (ticker->queue->initialized) { return; } + if (ticker->queue->suspended) { + return; + } ticker->interface->init(); @@ -70,6 +73,7 @@ static void initialize(const ticker_data_t *ticker) ticker->queue->max_delta_us = max_delta_us; ticker->queue->present_time = 0; ticker->queue->dispatching = false; + ticker->queue->suspended = false; ticker->queue->initialized = true; update_present_time(ticker); @@ -121,6 +125,9 @@ static us_timestamp_t convert_timestamp(us_timestamp_t ref, timestamp_t timestam static void update_present_time(const ticker_data_t *const ticker) { ticker_event_queue_t *queue = ticker->queue; + if (queue->suspended) { + return; + } uint32_t ticker_time = ticker->interface->read(); if (ticker_time == ticker->queue->tick_last_read) { // No work to do @@ -230,7 +237,7 @@ int _ticker_match_interval_passed(timestamp_t prev_tick, timestamp_t cur_tick, t static void schedule_interrupt(const ticker_data_t *const ticker) { ticker_event_queue_t *queue = ticker->queue; - if (ticker->queue->dispatching) { + if (queue->suspended || ticker->queue->dispatching) { // Don't schedule the next interrupt until dispatching is // finished. This prevents repeated calls to interface->set_interrupt return; @@ -285,6 +292,10 @@ void ticker_irq_handler(const ticker_data_t *const ticker) core_util_critical_section_enter(); ticker->interface->clear_interrupt(); + if (ticker->queue->suspended) { + core_util_critical_section_exit(); + return; + } /* Go through all the pending TimerEvents */ ticker->queue->dispatching = true; @@ -430,3 +441,29 @@ int ticker_get_next_timestamp(const ticker_data_t *const data, timestamp_t *time return ret; } + +void ticker_suspend(const ticker_data_t *const ticker) +{ + core_util_critical_section_enter(); + + ticker->queue->suspended = true; + + core_util_critical_section_exit(); +} + +void ticker_resume(const ticker_data_t *const ticker) +{ + core_util_critical_section_enter(); + + ticker->queue->suspended = false; + if (ticker->queue->initialized) { + ticker->queue->tick_last_read = ticker->interface->read(); + + update_present_time(ticker); + schedule_interrupt(ticker); + } else { + initialize(ticker); + } + + core_util_critical_section_exit(); +} diff --git a/hal/ticker_api.h b/hal/ticker_api.h index 1ab8b0e32be..98b2786ee35 100644 --- a/hal/ticker_api.h +++ b/hal/ticker_api.h @@ -82,6 +82,7 @@ typedef struct { us_timestamp_t present_time; /**< Store the timestamp used for present time */ bool initialized; /**< Indicate if the instance is initialized */ bool dispatching; /**< The function ticker_irq_handler is dispatching */ + bool suspended; /**< Indicate if the instance is suspended */ uint8_t frequency_shifts; /**< If frequency is a value of 2^n, this is n, otherwise 0 */ } ticker_event_queue_t; @@ -184,6 +185,27 @@ us_timestamp_t ticker_read_us(const ticker_data_t *const ticker); */ int ticker_get_next_timestamp(const ticker_data_t *const ticker, timestamp_t *timestamp); +/** Suspend this ticker + * + * When suspended reads will always return the same time and no + * events will be dispatched. When suspended the common layer + * will only ever call the interface function clear_interrupt() + * and that is only if ticker_irq_handler is called. + * + * + * @param ticker The ticker object. + */ +void ticker_suspend(const ticker_data_t *const ticker); + +/** Resume this ticker + * + * When resumed the ticker will ignore any time that has passed + * and continue counting up where it left off. + * + * @param ticker The ticker object. + */ +void ticker_resume(const ticker_data_t *const ticker); + /* Private functions * * @cond PRIVATE From 1bbf43ad38d16e22468ea74e0b11058aa36d3eeb Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Sat, 4 Aug 2018 17:33:09 -0500 Subject: [PATCH 2/7] Add test for ticker suspend and resume Unit test the functions ticker_suspend and ticker_resume. --- TESTS/mbed_hal/ticker/main.cpp | 80 ++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/TESTS/mbed_hal/ticker/main.cpp b/TESTS/mbed_hal/ticker/main.cpp index 6d5708af53e..754728cc27f 100644 --- a/TESTS/mbed_hal/ticker/main.cpp +++ b/TESTS/mbed_hal/ticker/main.cpp @@ -2268,6 +2268,82 @@ static void test_match_interval_passed_table() } } +/** + * Check that suspend and resume work as expected + * + * Check the following + * -time does not change while suspended + * -restricted interface functions are not called + * -scheduling resumes correctly + */ +static void test_suspend_resume() +{ + ticker_set_handler(&ticker_stub, NULL); + + interface_stub.timestamp = 1000; + us_timestamp_t start = ticker_read_us(&ticker_stub); + TEST_ASSERT_EQUAL(1000, start); + + /* Reset call count */ + interface_stub.init_call = 0; + interface_stub.read_call = 0; + interface_stub.set_interrupt_call = 0; + + /* Suspend the ticker */ + ticker_suspend(&ticker_stub); + const timestamp_t suspend_time = queue_stub.present_time; + + + /* Simulate time passing */ + interface_stub.timestamp = 1500; + us_timestamp_t next = ticker_read_us(&ticker_stub); + + /* Time should not have passed and no calls to interface should have been made */ + TEST_ASSERT_EQUAL(start, next); + TEST_ASSERT_EQUAL(0, interface_stub.init_call); + TEST_ASSERT_EQUAL(0, interface_stub.read_call); + TEST_ASSERT_EQUAL(0, interface_stub.disable_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.clear_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.set_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.fire_interrupt_call); + + + /* Simulate a reinit (time reset to 0) */ + interface_stub.timestamp = 0; + next = ticker_read_us(&ticker_stub); + + /* Time should not have passed and no calls to interface should have been made */ + TEST_ASSERT_EQUAL(start, next); + TEST_ASSERT_EQUAL(0, interface_stub.init_call); + TEST_ASSERT_EQUAL(0, interface_stub.read_call); + TEST_ASSERT_EQUAL(0, interface_stub.disable_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.clear_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.set_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.fire_interrupt_call); + + + /* Insert an event in the past and future */ + ticker_event_t event_past = { 0 }; + const timestamp_t event_past_timestamp = suspend_time - 10; + ticker_insert_event_us(&ticker_stub, &event_past, event_past_timestamp, 0); + + ticker_event_t event_future = { 0 }; + const timestamp_t event_future_timestamp = suspend_time + 10; + ticker_insert_event_us(&ticker_stub, &event_future, event_future_timestamp, 0); + + TEST_ASSERT_EQUAL(0, interface_stub.init_call); + TEST_ASSERT_EQUAL(0, interface_stub.read_call); + TEST_ASSERT_EQUAL(0, interface_stub.disable_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.clear_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.set_interrupt_call); + TEST_ASSERT_EQUAL(0, interface_stub.fire_interrupt_call); + + /* Resume and verify everything starts again */ + ticker_resume(&ticker_stub); + TEST_ASSERT_EQUAL(suspend_time, queue_stub.present_time); + TEST_ASSERT_EQUAL(1, interface_stub.fire_interrupt_call); +} + static const case_t cases[] = { MAKE_TEST_CASE("ticker initialization", test_ticker_initialization), MAKE_TEST_CASE( @@ -2376,6 +2452,10 @@ static const case_t cases[] = { MAKE_TEST_CASE( "test_match_interval_passed_table", test_match_interval_passed_table + ), + MAKE_TEST_CASE( + "test_suspend_resume", + test_suspend_resume ) }; From 472ababfefa998871beff773951ab499cbc6fea3 Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Thu, 12 Jul 2018 11:36:13 -0500 Subject: [PATCH 3/7] Update deep sleep lock check in tests When the define LPTICKER_DELAY_TICKS is set deep sleep can be randomly disallowed when using the low power ticker. This is because a Timer object, which locks deep sleep, is used to protect from back-to-back writes to lp tickers which can't support that. This causes tests which assert that deep sleep is allowed to intermittently fail. To fix this intermittent failure this patch adds the function sleep_manager_can_deep_sleep_test_check() which checks if deep sleep is allowed over a duration. It updates all the tests to use sleep_manager_can_deep_sleep_test_check() rather than sleep_manager_can_deep_sleep() so the tests work even if deep sleep is spuriously blocked. --- TESTS/mbed_drivers/sleep_lock/main.cpp | 44 +++++++++---------- TESTS/mbed_drivers/timeout/timeout_tests.h | 4 +- TESTS/mbed_hal/lp_ticker/main.cpp | 2 +- TESTS/mbed_hal/rtc/main.cpp | 2 +- TESTS/mbed_hal/sleep_manager/main.cpp | 6 +-- .../sleep_manager_racecondition/main.cpp | 4 +- TESTS/mbedmicro-rtos-mbed/systimer/main.cpp | 2 +- hal/mbed_sleep_manager.c | 14 ++++++ platform/mbed_power_mgmt.h | 11 +++++ 9 files changed, 57 insertions(+), 32 deletions(-) diff --git a/TESTS/mbed_drivers/sleep_lock/main.cpp b/TESTS/mbed_drivers/sleep_lock/main.cpp index 234c70dc2d2..7986845f578 100644 --- a/TESTS/mbed_drivers/sleep_lock/main.cpp +++ b/TESTS/mbed_drivers/sleep_lock/main.cpp @@ -29,90 +29,90 @@ using namespace utest::v1; void deep_sleep_lock_lock_test() { - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Check basic usage works DeepSleepLock lock; - TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check()); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Check that unlock and lock change can deep sleep as expected DeepSleepLock lock; - TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check()); lock.unlock(); - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); lock.lock(); - TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check()); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Check that unlock releases sleep based on count DeepSleepLock lock; lock.lock(); lock.lock(); lock.unlock(); - TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check()); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Check that unbalanced locks do not leave deep sleep locked DeepSleepLock lock; lock.lock(); - TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check()); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); } void timer_lock_test() { - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Just creating a timer object does not lock sleep Timer timer; - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Starting a timer does lock sleep Timer timer; timer.start(); - TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(false, sleep_manager_can_deep_sleep_test_check()); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Stopping a timer after starting it allows sleep Timer timer; timer.start(); timer.stop(); - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Starting a timer multiple times still lets you sleep Timer timer; timer.start(); timer.start(); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); { // Stopping a timer multiple times still lets you sleep Timer timer; timer.start(); timer.stop(); timer.stop(); - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); } - TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep()); + TEST_ASSERT_EQUAL(true, sleep_manager_can_deep_sleep_test_check()); } Case cases[] = { diff --git a/TESTS/mbed_drivers/timeout/timeout_tests.h b/TESTS/mbed_drivers/timeout/timeout_tests.h index 8f5d9abdf13..4c1311d85da 100644 --- a/TESTS/mbed_drivers/timeout/timeout_tests.h +++ b/TESTS/mbed_drivers/timeout/timeout_tests.h @@ -263,7 +263,7 @@ void test_sleep(void) timer.start(); timeout.attach_callback(mbed::callback(sem_callback, &sem), delay_us); - bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); + bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); TEST_ASSERT_FALSE_MESSAGE(deep_sleep_allowed, "Deep sleep should be disallowed"); while (sem.wait(0) != 1) { sleep(); @@ -322,7 +322,7 @@ void test_deepsleep(void) timer.start(); timeout.attach_callback(mbed::callback(sem_callback, &sem), delay_us); - bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); + bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed"); while (sem.wait(0) != 1) { sleep(); diff --git a/TESTS/mbed_hal/lp_ticker/main.cpp b/TESTS/mbed_hal/lp_ticker/main.cpp index f8cd8f1abf9..0d9d55acbfe 100644 --- a/TESTS/mbed_hal/lp_ticker/main.cpp +++ b/TESTS/mbed_hal/lp_ticker/main.cpp @@ -130,7 +130,7 @@ void lp_ticker_deepsleep_test() * tick_count + TICKER_INT_VAL. */ lp_ticker_set_interrupt(tick_count + TICKER_INT_VAL); - TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep()); + TEST_ASSERT_TRUE(sleep_manager_can_deep_sleep_test_check()); while (!intFlag) { sleep(); diff --git a/TESTS/mbed_hal/rtc/main.cpp b/TESTS/mbed_hal/rtc/main.cpp index 87cc3acf3d1..bd65e38cd4f 100644 --- a/TESTS/mbed_hal/rtc/main.cpp +++ b/TESTS/mbed_hal/rtc/main.cpp @@ -74,7 +74,7 @@ void rtc_sleep_test_support(bool deepsleep_mode) timeout.attach(callback, DELAY_4S); - TEST_ASSERT(sleep_manager_can_deep_sleep() == deepsleep_mode); + TEST_ASSERT(sleep_manager_can_deep_sleep_test_check() == deepsleep_mode); while (!expired) { sleep(); diff --git a/TESTS/mbed_hal/sleep_manager/main.cpp b/TESTS/mbed_hal/sleep_manager/main.cpp index c04daca78c1..3d3e392b50c 100644 --- a/TESTS/mbed_hal/sleep_manager/main.cpp +++ b/TESTS/mbed_hal/sleep_manager/main.cpp @@ -25,15 +25,15 @@ using namespace utest::v1; void sleep_manager_deepsleep_counter_test() { - bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); + bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); TEST_ASSERT_TRUE(deep_sleep_allowed); sleep_manager_lock_deep_sleep(); - deep_sleep_allowed = sleep_manager_can_deep_sleep(); + deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); TEST_ASSERT_FALSE(deep_sleep_allowed); sleep_manager_unlock_deep_sleep(); - deep_sleep_allowed = sleep_manager_can_deep_sleep(); + deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); TEST_ASSERT_TRUE(deep_sleep_allowed); } diff --git a/TESTS/mbed_hal/sleep_manager_racecondition/main.cpp b/TESTS/mbed_hal/sleep_manager_racecondition/main.cpp index 710f3caf16a..d43492c0525 100644 --- a/TESTS/mbed_hal/sleep_manager_racecondition/main.cpp +++ b/TESTS/mbed_hal/sleep_manager_racecondition/main.cpp @@ -55,7 +55,7 @@ void sleep_manager_multithread_test() t2.join(); } - bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); + bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed"); } @@ -83,7 +83,7 @@ void sleep_manager_irq_test() timer.stop(); } - bool deep_sleep_allowed = sleep_manager_can_deep_sleep(); + bool deep_sleep_allowed = sleep_manager_can_deep_sleep_test_check(); TEST_ASSERT_TRUE_MESSAGE(deep_sleep_allowed, "Deep sleep should be allowed"); } diff --git a/TESTS/mbedmicro-rtos-mbed/systimer/main.cpp b/TESTS/mbedmicro-rtos-mbed/systimer/main.cpp index 27cdae531a7..8a25ad28719 100644 --- a/TESTS/mbedmicro-rtos-mbed/systimer/main.cpp +++ b/TESTS/mbedmicro-rtos-mbed/systimer/main.cpp @@ -313,7 +313,7 @@ void test_deepsleep(void) lptimer.start(); st.schedule_tick(TEST_TICKS); - TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "Deep sleep should be allowed"); + TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep_test_check(), "Deep sleep should be allowed"); while (st.sem_wait(0) != 1) { sleep(); } diff --git a/hal/mbed_sleep_manager.c b/hal/mbed_sleep_manager.c index d485965da67..30e2d74837c 100644 --- a/hal/mbed_sleep_manager.c +++ b/hal/mbed_sleep_manager.c @@ -21,6 +21,7 @@ #include "mbed_error.h" #include "mbed_debug.h" #include "mbed_stats.h" +#include "us_ticker_api.h" #include "lp_ticker_api.h" #include #include @@ -183,6 +184,19 @@ bool sleep_manager_can_deep_sleep(void) return deep_sleep_lock == 0 ? true : false; } +bool sleep_manager_can_deep_sleep_test_check() +{ + const uint32_t check_time_us = 2000; + const ticker_data_t *const ticker = get_us_ticker_data(); + uint32_t start = ticker_read(ticker); + while ((ticker_read(ticker) - start) < check_time_us) { + if (sleep_manager_can_deep_sleep()) { + return true; + } + } + return false; +} + void sleep_manager_sleep_auto(void) { #ifdef MBED_SLEEP_TRACING_ENABLED diff --git a/platform/mbed_power_mgmt.h b/platform/mbed_power_mgmt.h index 5d30348dcea..026cbf214d1 100644 --- a/platform/mbed_power_mgmt.h +++ b/platform/mbed_power_mgmt.h @@ -122,6 +122,17 @@ void sleep_manager_unlock_deep_sleep_internal(void); */ bool sleep_manager_can_deep_sleep(void); +/** Check if the target can deep sleep within a period of time + * + * This function in intended for use in testing. The amount + * of time this functions waits for deeps sleep to be available + * is currently 2ms. This may change in the future depending + * on testing requirements. + * + * @return true if a target can go to deepsleep, false otherwise + */ +bool sleep_manager_can_deep_sleep_test_check(void); + /** Enter auto selected sleep mode. It chooses the sleep or deeepsleep modes based * on the deepsleep locking counter * From adc64cccacfd93f04f17dd2f01f278c020b8831d Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Thu, 12 Jul 2018 11:25:07 -0500 Subject: [PATCH 4/7] Update low power ticker wrapper Update the low power ticker wrapper code so it does not violate any properties of the ticker specification. In specific this patch fixes the following: - Prevent spurious interrupts - Fire interrupt only when the ticker times increments to or past the value set by ticker_set_interrupt - Disable interrupts when ticker_init is called --- hal/LowPowerTickerWrapper.cpp | 290 +++++++++++++++++++++++++++++++++ hal/LowPowerTickerWrapper.h | 239 +++++++++++++++++++++++++++ hal/mbed_lp_ticker_api.c | 15 +- hal/mbed_lp_ticker_wrapper.cpp | 173 ++++++++------------ hal/mbed_lp_ticker_wrapper.h | 78 +++++++++ 5 files changed, 688 insertions(+), 107 deletions(-) create mode 100644 hal/LowPowerTickerWrapper.cpp create mode 100644 hal/LowPowerTickerWrapper.h create mode 100644 hal/mbed_lp_ticker_wrapper.h diff --git a/hal/LowPowerTickerWrapper.cpp b/hal/LowPowerTickerWrapper.cpp new file mode 100644 index 00000000000..fdbdf2c01f4 --- /dev/null +++ b/hal/LowPowerTickerWrapper.cpp @@ -0,0 +1,290 @@ +/* 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. + */ +#include "hal/LowPowerTickerWrapper.h" +#include "platform/Callback.h" + +LowPowerTickerWrapper::LowPowerTickerWrapper(const ticker_data_t *data, const ticker_interface_t *interface, uint32_t min_cycles_between_writes, uint32_t min_cycles_until_match) + : _intf(data->interface), _min_count_between_writes(min_cycles_between_writes + 1), _min_count_until_match(min_cycles_until_match + 1), _suspended(false) +{ + core_util_critical_section_enter(); + + this->data.interface = interface; + this->data.queue = data->queue; + _reset(); + + core_util_critical_section_exit(); +} + +void LowPowerTickerWrapper::irq_handler(ticker_irq_handler_type handler) +{ + core_util_critical_section_enter(); + + if (_suspended) { + if (handler) { + handler(&data); + } + core_util_critical_section_exit(); + return; + } + + if (_pending_fire_now || _match_check(_intf->read())) { + _timeout.detach(); + _pending_timeout = false; + _pending_match = false; + _pending_fire_now = false; + if (handler) { + handler(&data); + } + } else { + // Spurious interrupt + _intf->clear_interrupt(); + } + + core_util_critical_section_exit(); +} + +void LowPowerTickerWrapper::suspend() +{ + core_util_critical_section_enter(); + + // Wait until rescheduling is allowed + while (!_set_interrupt_allowed) { + timestamp_t current = _intf->read(); + if (((current - _last_actual_set_interrupt) & _mask) >= _min_count_between_writes) { + _set_interrupt_allowed = true; + } + } + + _reset(); + _suspended = true; + + core_util_critical_section_exit(); +} + +void LowPowerTickerWrapper::resume() +{ + core_util_critical_section_enter(); + + _suspended = false; + + core_util_critical_section_exit(); +} + +bool LowPowerTickerWrapper::timeout_pending() +{ + core_util_critical_section_enter(); + + bool pending = _pending_timeout; + + core_util_critical_section_exit(); + return pending; +} + +void LowPowerTickerWrapper::init() +{ + core_util_critical_section_enter(); + + _reset(); + _intf->init(); + + core_util_critical_section_exit(); +} + +void LowPowerTickerWrapper::free() +{ + core_util_critical_section_enter(); + + _reset(); + _intf->free(); + + core_util_critical_section_exit(); +} + +uint32_t LowPowerTickerWrapper::read() +{ + core_util_critical_section_enter(); + + timestamp_t current = _intf->read(); + if (_match_check(current)) { + _intf->fire_interrupt(); + } + + core_util_critical_section_exit(); + return current; +} + +void LowPowerTickerWrapper::set_interrupt(timestamp_t timestamp) +{ + core_util_critical_section_enter(); + + _last_set_interrupt = _intf->read(); + _cur_match_time = timestamp; + _pending_match = true; + _schedule_match(_last_set_interrupt); + + core_util_critical_section_exit(); +} + +void LowPowerTickerWrapper::disable_interrupt() +{ + core_util_critical_section_enter(); + + _intf->disable_interrupt(); + + core_util_critical_section_exit(); +} + +void LowPowerTickerWrapper::clear_interrupt() +{ + core_util_critical_section_enter(); + + _intf->clear_interrupt(); + + core_util_critical_section_exit(); +} + +void LowPowerTickerWrapper::fire_interrupt() +{ + core_util_critical_section_enter(); + + _pending_fire_now = 1; + _intf->fire_interrupt(); + + core_util_critical_section_exit(); +} + +const ticker_info_t *LowPowerTickerWrapper::get_info() +{ + + core_util_critical_section_enter(); + + const ticker_info_t *info = _intf->get_info(); + + core_util_critical_section_exit(); + return info; +} + +void LowPowerTickerWrapper::_reset() +{ + MBED_ASSERT(core_util_in_critical_section()); + + _timeout.detach(); + _pending_timeout = false; + _pending_match = false; + _pending_fire_now = false; + _set_interrupt_allowed = true; + _cur_match_time = 0; + _last_set_interrupt = 0; + _last_actual_set_interrupt = 0; + + const ticker_info_t *info = _intf->get_info(); + if (info->bits >= 32) { + _mask = 0xffffffff; + } else { + _mask = ((uint64_t)1 << info->bits) - 1; + } + + // Round us_per_tick up + _us_per_tick = (1000000 + info->frequency - 1) / info->frequency; +} + +void LowPowerTickerWrapper::_timeout_handler() +{ + core_util_critical_section_enter(); + _pending_timeout = false; + + timestamp_t current = _intf->read(); + if (_ticker_match_interval_passed(_last_set_interrupt, current, _cur_match_time)) { + _intf->fire_interrupt(); + } else { + _schedule_match(current); + } + + core_util_critical_section_exit(); +} + +bool LowPowerTickerWrapper::_match_check(timestamp_t current) +{ + MBED_ASSERT(core_util_in_critical_section()); + + if (!_pending_match) { + return false; + } + return _ticker_match_interval_passed(_last_set_interrupt, current, _cur_match_time); +} + +uint32_t LowPowerTickerWrapper::_lp_ticks_to_us(uint32_t ticks) +{ + MBED_ASSERT(core_util_in_critical_section()); + + // Add 4 microseconds to round up the micro second ticker time (which has a frequency of at least 250KHz - 4us period) + return _us_per_tick * ticks + 4; +} + +void LowPowerTickerWrapper::_schedule_match(timestamp_t current) +{ + MBED_ASSERT(core_util_in_critical_section()); + + // Check if _intf->set_interrupt is allowed + if (!_set_interrupt_allowed) { + if (((current - _last_actual_set_interrupt) & _mask) >= _min_count_between_writes) { + _set_interrupt_allowed = true; + } + } + + uint32_t cycles_until_match = (_cur_match_time - _last_set_interrupt) & _mask; + bool too_close = cycles_until_match < _min_count_until_match; + + if (!_set_interrupt_allowed) { + + // Can't use _intf->set_interrupt so use microsecond Timeout instead + uint32_t ticks = cycles_until_match < _min_count_until_match ? cycles_until_match : _min_count_until_match; + _timeout.attach_us(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks)); + _pending_timeout = true; + return; + } + + if (!too_close) { + + // Schedule LP ticker + _intf->set_interrupt(_cur_match_time); + current = _intf->read(); + _last_actual_set_interrupt = current; + _set_interrupt_allowed = false; + + // Check for overflow + uint32_t new_cycles_until_match = (_cur_match_time - current) & _mask; + if (new_cycles_until_match > cycles_until_match) { + // Overflow so fire now + _intf->fire_interrupt(); + return; + } + + // Update variables with new time + cycles_until_match = new_cycles_until_match; + too_close = cycles_until_match < _min_count_until_match; + } + + if (too_close) { + + // Low power ticker incremented to less than _min_count_until_match + // so low power ticker may not fire. Use Timeout to ensure it does fire. + uint32_t ticks = cycles_until_match < _min_count_until_match ? cycles_until_match : _min_count_until_match; + _timeout.attach_us(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks)); + _pending_timeout = true; + return; + } +} diff --git a/hal/LowPowerTickerWrapper.h b/hal/LowPowerTickerWrapper.h new file mode 100644 index 00000000000..aac8d70d171 --- /dev/null +++ b/hal/LowPowerTickerWrapper.h @@ -0,0 +1,239 @@ + +/** \addtogroup hal */ +/** @{*/ +/* 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. + */ +#ifndef MBED_LOW_POWER_TICKER_WRAPPER_H +#define MBED_LOW_POWER_TICKER_WRAPPER_H + +#include "device.h" + +#include "hal/ticker_api.h" +#include "hal/us_ticker_api.h" +#include "drivers/Timeout.h" + + +class LowPowerTickerWrapper { +public: + + + /** + * Create a new wrapped low power ticker object + * + * @param data Low power ticker data to wrap + * @param interface new ticker interface functions + * @param min_cycles_between_writes The number of whole low power clock periods + * which must complete before subsequent calls to set_interrupt + * @param min_cycles_until_match The minimum number of whole low power clock periods + * from the current time for which the match timestamp passed to set_interrupt is + * guaranteed to fire. + * + * N = min_cycles_between_writes + * + * 0 1 N - 1 N N + 1 N + 2 N + 3 + * |-------|------...------|-------|-------|-------|-------| + * ^ ^ + * | | + * set_interrupt Next set_interrupt allowed + * + * N = min_cycles_until_match + * + * 0 1 N - 1 N N + 1 N + 2 N + 3 + * |-------|------...------|-------|-------|-------|-------| + * ^ ^ + * | | + * set_interrupt Earliest match timestamp allowed + * + * + */ + + LowPowerTickerWrapper(const ticker_data_t *data, const ticker_interface_t *interface, uint32_t min_cycles_between_writes, uint32_t min_cycles_until_match); + + /** + * Interrupt handler called by the underlying driver/hardware + * + * @param handler The callback which would normally be called by the underlying driver/hardware + */ + void irq_handler(ticker_irq_handler_type handler); + + /** + * Suspend wrapper operation and pass through interrupts. + * + * This stops to wrapper layer from using the microsecond ticker. + * This should be called before using the low power ticker APIs directly. + */ + void suspend(); + + /** + * Resume wrapper operation and filter interrupts normally + */ + void resume(); + + /** + * Check if a Timeout object is being used + * + * @return true if Timeout is used for scheduling false otherwise + */ + bool timeout_pending(); + + /* + * Implementation of ticker_init + */ + void init(); + + /* + * Implementation of free + */ + void free(); + + /* + * Implementation of read + */ + uint32_t read(); + + /* + * Implementation of set_interrupt + */ + void set_interrupt(timestamp_t timestamp); + + /* + * Implementation of disable_interrupt + */ + void disable_interrupt(); + + /* + * Implementation of clear_interrupt + */ + void clear_interrupt(); + + /* + * Implementation of fire_interrupt + */ + void fire_interrupt(); + + /* + * Implementation of get_info + */ + const ticker_info_t *get_info(); + + ticker_data_t data; + +private: + mbed::Timeout _timeout; + const ticker_interface_t *const _intf; + + /* + * The number of low power clock cycles which must pass between subsequent + * calls to intf->set_interrupt + */ + const uint32_t _min_count_between_writes; + + /* + * The minimum number of low power clock cycles in the future that + * a match value can be set to and still fire + */ + const uint32_t _min_count_until_match; + + /* + * Flag to indicate if the timer is suspended + */ + bool _suspended; + + /* + * _cur_match_time is valid and Timeout is scheduled to fire + */ + bool _pending_timeout; + + /* + * set_interrupt has been called and _cur_match_time is valid + */ + bool _pending_match; + + /* + * The function LowPowerTickerWrapper::fire_interrupt has been called + * and an interrupt is expected. + */ + bool _pending_fire_now; + + /* + * It is safe to call intf->set_interrupt + */ + bool _set_interrupt_allowed; + + /* + * Last value written by LowPowerTickerWrapper::set_interrupt + */ + timestamp_t _cur_match_time; + + /* + * Time of last call to LowPowerTickerWrapper::set_interrupt + */ + uint32_t _last_set_interrupt; + + /* + * Time of last call to intf->set_interrupt + */ + uint32_t _last_actual_set_interrupt; + + /* + * Mask of valid bits from intf->read() + */ + uint32_t _mask; + + /* + * Microsecond per low power tick (rounded up) + */ + uint32_t _us_per_tick; + + + void _reset(); + + /** + * Set the low power ticker match time when hardware is ready + * + * This event is scheduled to set the lp timer after the previous write + * has taken effect and it is safe to write a new value without blocking. + * If the time has already passed then this function fires and interrupt + * immediately. + */ + void _timeout_handler(); + + /* + * Check match time has passed + */ + bool _match_check(timestamp_t current); + + /* + * Convert low power ticks to approximate microseconds + * + * This value is always larger or equal to exact value. + */ + uint32_t _lp_ticks_to_us(uint32_t); + + /* + * Schedule a match interrupt to fire at the correct time + * + * @param current The current low power ticker time + */ + void _schedule_match(timestamp_t current); + +}; + +#endif + +/** @}*/ + + diff --git a/hal/mbed_lp_ticker_api.c b/hal/mbed_lp_ticker_api.c index fbf72c33411..477c1b13875 100644 --- a/hal/mbed_lp_ticker_api.c +++ b/hal/mbed_lp_ticker_api.c @@ -14,11 +14,10 @@ * limitations under the License. */ #include "hal/lp_ticker_api.h" +#include "hal/mbed_lp_ticker_wrapper.h" #if DEVICE_LPTICKER -void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp); - static ticker_event_queue_t events = { 0 }; static ticker_irq_handler_type irq_handler = ticker_irq_handler; @@ -28,11 +27,7 @@ static const ticker_interface_t lp_interface = { .read = lp_ticker_read, .disable_interrupt = lp_ticker_disable_interrupt, .clear_interrupt = lp_ticker_clear_interrupt, -#if LPTICKER_DELAY_TICKS > 0 - .set_interrupt = lp_ticker_set_interrupt_wrapper, -#else .set_interrupt = lp_ticker_set_interrupt, -#endif .fire_interrupt = lp_ticker_fire_interrupt, .get_info = lp_ticker_get_info, .free = lp_ticker_free, @@ -45,7 +40,11 @@ static const ticker_data_t lp_data = { const ticker_data_t *get_lp_ticker_data(void) { +#if LPTICKER_DELAY_TICKS > 0 + return get_lp_ticker_wrapper_data(&lp_data); +#else return &lp_data; +#endif } ticker_irq_handler_type set_lp_ticker_irq_handler(ticker_irq_handler_type ticker_irq_handler) @@ -59,9 +58,13 @@ ticker_irq_handler_type set_lp_ticker_irq_handler(ticker_irq_handler_type ticker void lp_ticker_irq_handler(void) { +#if LPTICKER_DELAY_TICKS > 0 + lp_ticker_wrapper_irq_handler(irq_handler); +#else if (irq_handler) { irq_handler(&lp_data); } +#endif } #endif diff --git a/hal/mbed_lp_ticker_wrapper.cpp b/hal/mbed_lp_ticker_wrapper.cpp index 9c1a2b47289..403141fceaa 100644 --- a/hal/mbed_lp_ticker_wrapper.cpp +++ b/hal/mbed_lp_ticker_wrapper.cpp @@ -13,144 +13,115 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "hal/lp_ticker_api.h" +#include "hal/mbed_lp_ticker_wrapper.h" #if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) -#include "Timeout.h" -#include "mbed_critical.h" - -static const timestamp_t min_delta = LPTICKER_DELAY_TICKS; +#include "hal/LowPowerTickerWrapper.h" +#include "platform/mbed_critical.h" +// Do not use SingletonPtr since this must be initialized in a critical section +static LowPowerTickerWrapper *ticker_wrapper; +static uint64_t ticker_wrapper_data[(sizeof(LowPowerTickerWrapper) + 7) / 8]; static bool init = false; -static bool pending = false; -static bool timeout_pending = false; -static timestamp_t last_set_interrupt = 0; -static timestamp_t last_request = 0; -static timestamp_t next = 0; -static timestamp_t mask; -static timestamp_t reschedule_us; +static void lp_ticker_wrapper_init() +{ + ticker_wrapper->init(); +} -// Do not use SingletonPtr since this must be initialized in a critical section -static mbed::Timeout *timeout; -static uint64_t timeout_data[sizeof(mbed::Timeout) / 8]; +static uint32_t lp_ticker_wrapper_read() +{ + return ticker_wrapper->read(); +} -/** - * Initialize variables - */ -static void init_local() +static void lp_ticker_wrapper_set_interrupt(timestamp_t timestamp) { - MBED_ASSERT(core_util_in_critical_section()); + ticker_wrapper->set_interrupt(timestamp); +} - const ticker_info_t *info = lp_ticker_get_info(); - if (info->bits >= 32) { - mask = 0xffffffff; - } else { - mask = ((uint64_t)1 << info->bits) - 1; - } +static void lp_ticker_wrapper_disable_interrupt() +{ + ticker_wrapper->disable_interrupt(); +} - // Round us_per_tick up - timestamp_t us_per_tick = (1000000 + info->frequency - 1) / info->frequency; +static void lp_ticker_wrapper_clear_interrupt() +{ + ticker_wrapper->clear_interrupt(); +} - // Add 1 tick to the min delta for the case where the clock transitions after you read it - // Add 4 microseconds to round up the micro second ticker time (which has a frequency of at least 250KHz - 4us period) - reschedule_us = (min_delta + 1) * us_per_tick + 4; +static void lp_ticker_wrapper_fire_interrupt() +{ + ticker_wrapper->fire_interrupt(); +} - timeout = new (timeout_data) mbed::Timeout(); +static const ticker_info_t *lp_ticker_wrapper_get_info() +{ + return ticker_wrapper->get_info(); } -/** - * Call lp_ticker_set_interrupt with a value that is guaranteed to fire - * - * Assumptions - * -Only one low power clock tick can pass from the last read (last_read) - * -The closest an interrupt can fire is max_delta + 1 - * - * @param last_read The last value read from lp_ticker_read - * @param timestamp The timestamp to trigger the interrupt at - */ -static void set_interrupt_safe(timestamp_t last_read, timestamp_t timestamp) +static void lp_ticker_wrapper_free() { - MBED_ASSERT(core_util_in_critical_section()); - uint32_t delta = (timestamp - last_read) & mask; - if (delta < min_delta + 2) { - timestamp = (last_read + min_delta + 2) & mask; - } - lp_ticker_set_interrupt(timestamp); + ticker_wrapper->free(); } -/** - * Set the low power ticker match time when hardware is ready - * - * This event is scheduled to set the lp timer after the previous write - * has taken effect and it is safe to write a new value without blocking. - * If the time has already passed then this function fires and interrupt - * immediately. - */ -static void set_interrupt_later() +static const ticker_interface_t lp_interface = { + lp_ticker_wrapper_init, + lp_ticker_wrapper_read, + lp_ticker_wrapper_disable_interrupt, + lp_ticker_wrapper_clear_interrupt, + lp_ticker_wrapper_set_interrupt, + lp_ticker_wrapper_fire_interrupt, + lp_ticker_wrapper_free, + lp_ticker_wrapper_get_info +}; + +void lp_ticker_wrapper_irq_handler(ticker_irq_handler_type handler) { core_util_critical_section_enter(); - timestamp_t current = lp_ticker_read(); - if (_ticker_match_interval_passed(last_request, current, next)) { - lp_ticker_fire_interrupt(); - } else { - set_interrupt_safe(current, next); - last_set_interrupt = lp_ticker_read(); + if (!init) { + // Force ticker to initialize + get_lp_ticker_data(); } - timeout_pending = false; + + ticker_wrapper->irq_handler(handler); core_util_critical_section_exit(); } -/** - * Wrapper around lp_ticker_set_interrupt to prevent blocking - * - * Problems this function is solving: - * 1. Interrupt may not fire if set earlier than LPTICKER_DELAY_TICKS low power clock cycles - * 2. Setting the interrupt back-to-back will block - * - * This wrapper function prevents lp_ticker_set_interrupt from being called - * back-to-back and blocking while the first write is in progress. This function - * avoids that problem by scheduling a timeout event if the lp ticker is in the - * middle of a write operation. - * - * @param timestamp Time to call ticker irq - * @note this is a utility function and it's not required part of HAL implementation - */ -extern "C" void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp) +const ticker_data_t *get_lp_ticker_wrapper_data(const ticker_data_t *data) { core_util_critical_section_enter(); if (!init) { - init_local(); + ticker_wrapper = new (ticker_wrapper_data) LowPowerTickerWrapper(data, &lp_interface, LPTICKER_DELAY_TICKS, LPTICKER_DELAY_TICKS); init = true; } - timestamp_t current = lp_ticker_read(); - if (pending) { - // Check if pending should be cleared - if (((current - last_set_interrupt) & mask) >= min_delta) { - pending = false; - } + core_util_critical_section_exit(); + + return &ticker_wrapper->data; +} + +void lp_ticker_wrapper_suspend() +{ + if (!init) { + // Force ticker to initialize + get_lp_ticker_data(); } - if (pending || timeout_pending) { - next = timestamp; - last_request = current; - if (!timeout_pending) { - timeout->attach_us(set_interrupt_later, reschedule_us); - timeout_pending = true; - } - } else { - // Schedule immediately if nothing is pending - set_interrupt_safe(current, timestamp); - last_set_interrupt = lp_ticker_read(); - pending = true; + ticker_wrapper->suspend(); +} + +void lp_ticker_wrapper_resume() +{ + if (!init) { + // Force ticker to initialize + get_lp_ticker_data(); } - core_util_critical_section_exit(); + ticker_wrapper->resume(); } #endif diff --git a/hal/mbed_lp_ticker_wrapper.h b/hal/mbed_lp_ticker_wrapper.h new file mode 100644 index 00000000000..4b317e36c27 --- /dev/null +++ b/hal/mbed_lp_ticker_wrapper.h @@ -0,0 +1,78 @@ + +/** \addtogroup hal */ +/** @{*/ +/* 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. + */ +#ifndef MBED_LP_TICKER_WRAPPER_H +#define MBED_LP_TICKER_WRAPPER_H + +#include "device.h" + +#if DEVICE_LPTICKER + +#include "hal/ticker_api.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*ticker_irq_handler_type)(const ticker_data_t *const); + +/** + * Interrupt handler for the wrapped lp ticker + * + * @param handler the function which would normally be called by the + * lp ticker handler when it is not wrapped + */ +void lp_ticker_wrapper_irq_handler(ticker_irq_handler_type handler); + +/** + * Get wrapped lp ticker data + * + * @param data hardware low power ticker object + * @return wrapped low power ticker object + */ +const ticker_data_t *get_lp_ticker_wrapper_data(const ticker_data_t *data); + +/** + * Suspend the wrapper layer code + * + * Pass through all interrupts to the low power ticker and stop using + * the microsecond ticker. + */ +void lp_ticker_wrapper_suspend(void); + +/** + * Resume the wrapper layer code + * + * Resume operation of the wrapper layer. Interrupts will be filtered + * as normal and the microsecond timer will be used for interrupts scheduled + * too quickly back-to-back. + */ +void lp_ticker_wrapper_resume(void); + +/**@}*/ + +#ifdef __cplusplus +} +#endif + +#endif + +#endif + +/** @}*/ From f68958df4e089e678c90877da7a24b2513e8d614 Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Thu, 12 Jul 2018 11:48:16 -0500 Subject: [PATCH 5/7] Fix tests to work with LPTICKER_DELAY_TICKS Fix the HAL common_tickers and sleep tests so they work correctly when the define LPTICKER_DELAY_TICKS is set. --- TESTS/mbed_hal/common_tickers/main.cpp | 25 ++++++-- TESTS/mbed_hal/common_tickers_freq/main.cpp | 49 ++++++++++++--- TESTS/mbed_hal/lp_ticker/main.cpp | 33 ++++++++++- TESTS/mbed_hal/sleep/main.cpp | 66 ++++++++++++++------- 4 files changed, 136 insertions(+), 37 deletions(-) diff --git a/TESTS/mbed_hal/common_tickers/main.cpp b/TESTS/mbed_hal/common_tickers/main.cpp index 16b48d0c77e..ca539bd115c 100644 --- a/TESTS/mbed_hal/common_tickers/main.cpp +++ b/TESTS/mbed_hal/common_tickers/main.cpp @@ -20,6 +20,7 @@ #include "ticker_api_tests.h" #include "hal/us_ticker_api.h" #include "hal/lp_ticker_api.h" +#include "hal/mbed_lp_ticker_wrapper.h" #ifdef __cplusplus extern "C" { @@ -496,7 +497,15 @@ utest::v1::status_t us_ticker_setup(const Case *const source, const size_t index { intf = get_us_ticker_data()->interface; - OS_Tick_Disable(); + /* OS, common ticker and low power ticker wrapper + * may make use of us ticker so suspend them for this test */ + osKernelSuspend(); +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + /* Suspend the lp ticker wrapper since it makes use of the us ticker */ + ticker_suspend(get_lp_ticker_data()); + lp_ticker_wrapper_suspend(); +#endif + ticker_suspend(get_us_ticker_data()); intf->init(); @@ -515,7 +524,12 @@ utest::v1::status_t us_ticker_teardown(const Case *const source, const size_t pa prev_irq_handler = NULL; - OS_Tick_Enable(); + ticker_resume(get_us_ticker_data()); +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + lp_ticker_wrapper_resume(); + ticker_resume(get_lp_ticker_data()); +#endif + osKernelResume(0); return greentea_case_teardown_handler(source, passed, failed, reason); } @@ -525,7 +539,9 @@ utest::v1::status_t lp_ticker_setup(const Case *const source, const size_t index { intf = get_lp_ticker_data()->interface; - OS_Tick_Disable(); + /* OS and common ticker may make use of lp ticker so suspend them for this test */ + osKernelSuspend(); + ticker_suspend(get_lp_ticker_data()); intf->init(); @@ -544,7 +560,8 @@ utest::v1::status_t lp_ticker_teardown(const Case *const source, const size_t pa prev_irq_handler = NULL; - OS_Tick_Enable(); + ticker_resume(get_lp_ticker_data()); + osKernelResume(0); return greentea_case_teardown_handler(source, passed, failed, reason); } diff --git a/TESTS/mbed_hal/common_tickers_freq/main.cpp b/TESTS/mbed_hal/common_tickers_freq/main.cpp index c34527b2184..564fe323c53 100644 --- a/TESTS/mbed_hal/common_tickers_freq/main.cpp +++ b/TESTS/mbed_hal/common_tickers_freq/main.cpp @@ -28,6 +28,7 @@ #include "ticker_api_test_freq.h" #include "hal/us_ticker_api.h" #include "hal/lp_ticker_api.h" +#include "hal/mbed_lp_ticker_wrapper.h" #if !DEVICE_USTICKER #error [NOT_SUPPORTED] test not supported @@ -38,6 +39,7 @@ using namespace utest::v1; const ticker_interface_t *intf; +ticker_irq_handler_type prev_handler; static volatile unsigned int overflowCounter; @@ -106,43 +108,74 @@ void ticker_frequency_test() utest::v1::status_t us_ticker_case_setup_handler_t(const Case *const source, const size_t index_of_case) { +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + /* Suspend the lp ticker wrapper since it makes use of the us ticker */ + ticker_suspend(get_lp_ticker_data()); + lp_ticker_wrapper_suspend(); +#endif + ticker_suspend(get_us_ticker_data()); intf = get_us_ticker_data()->interface; - set_us_ticker_irq_handler(ticker_event_handler_stub); + prev_handler = set_us_ticker_irq_handler(ticker_event_handler_stub); return greentea_case_setup_handler(source, index_of_case); } +utest::v1::status_t us_ticker_case_teardown_handler_t(const Case *const source, const size_t passed, const size_t failed, + const failure_t reason) +{ + set_us_ticker_irq_handler(prev_handler); + ticker_resume(get_us_ticker_data()); +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + lp_ticker_wrapper_resume(); + ticker_resume(get_lp_ticker_data()); +#endif + return greentea_case_teardown_handler(source, passed, failed, reason); +} + #if DEVICE_LPTICKER utest::v1::status_t lp_ticker_case_setup_handler_t(const Case *const source, const size_t index_of_case) { + ticker_suspend(get_lp_ticker_data()); intf = get_lp_ticker_data()->interface; - set_lp_ticker_irq_handler(ticker_event_handler_stub); + prev_handler = set_lp_ticker_irq_handler(ticker_event_handler_stub); return greentea_case_setup_handler(source, index_of_case); } -#endif -utest::v1::status_t ticker_case_teardown_handler_t(const Case *const source, const size_t passed, const size_t failed, - const failure_t reason) +utest::v1::status_t lp_ticker_case_teardown_handler_t(const Case *const source, const size_t passed, const size_t failed, + const failure_t reason) { + set_lp_ticker_irq_handler(prev_handler); + ticker_resume(get_lp_ticker_data()); return greentea_case_teardown_handler(source, passed, failed, reason); } +#endif // Test cases Case cases[] = { Case("Microsecond ticker frequency test", us_ticker_case_setup_handler_t, ticker_frequency_test, - ticker_case_teardown_handler_t), + us_ticker_case_teardown_handler_t), #if DEVICE_LPTICKER Case("Low power ticker frequency test", lp_ticker_case_setup_handler_t, ticker_frequency_test, - ticker_case_teardown_handler_t), + lp_ticker_case_teardown_handler_t), #endif }; utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { + /* Suspend RTOS Kernel so the timers are not in use. */ + osKernelSuspend(); + GREENTEA_SETUP(120, "timing_drift_auto"); return greentea_test_setup_handler(number_of_cases); } -Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); +void greentea_test_teardown(const size_t passed, const size_t failed, const failure_t failure) +{ + osKernelResume(0); + + greentea_test_teardown_handler(passed, failed, failure); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown); int main() { diff --git a/TESTS/mbed_hal/lp_ticker/main.cpp b/TESTS/mbed_hal/lp_ticker/main.cpp index 0d9d55acbfe..58ff4acf7f1 100644 --- a/TESTS/mbed_hal/lp_ticker/main.cpp +++ b/TESTS/mbed_hal/lp_ticker/main.cpp @@ -20,6 +20,7 @@ #include "rtos.h" #include "lp_ticker_api_tests.h" #include "hal/lp_ticker_api.h" +#include "hal/mbed_lp_ticker_wrapper.h" #if !DEVICE_LPTICKER #error [NOT_SUPPORTED] Low power timer not supported for this target @@ -29,6 +30,8 @@ using namespace utest::v1; volatile int intFlag = 0; +ticker_irq_handler_type prev_handler; + #define US_PER_MS 1000 #define TICKER_GLITCH_TEST_TICKS 1000 @@ -113,8 +116,6 @@ void lp_ticker_deepsleep_test() { intFlag = 0; - set_lp_ticker_irq_handler(ticker_event_handler_stub); - lp_ticker_init(); /* Give some time Green Tea to finish UART transmission before entering @@ -157,6 +158,32 @@ void lp_ticker_glitch_test() } } +#if DEVICE_LPTICKER +utest::v1::status_t lp_ticker_deepsleep_test_setup_handler(const Case *const source, const size_t index_of_case) +{ + /* disable everything using the lp ticker for this test */ + osKernelSuspend(); + ticker_suspend(get_lp_ticker_data()); +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + lp_ticker_wrapper_suspend(); +#endif + prev_handler = set_lp_ticker_irq_handler(ticker_event_handler_stub); + return greentea_case_setup_handler(source, index_of_case); +} + +utest::v1::status_t lp_ticker_deepsleep_test_teardown_handler(const Case *const source, const size_t passed, const size_t failed, + const failure_t reason) +{ + set_lp_ticker_irq_handler(prev_handler); +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + lp_ticker_wrapper_resume(); +#endif + ticker_resume(get_lp_ticker_data()); + osKernelResume(0); + return greentea_case_teardown_handler(source, passed, failed, reason); +} +#endif + utest::v1::status_t test_setup(const size_t number_of_cases) { GREENTEA_SETUP(20, "default_auto"); @@ -166,7 +193,7 @@ utest::v1::status_t test_setup(const size_t number_of_cases) Case cases[] = { Case("lp ticker info test", lp_ticker_info_test), #if DEVICE_SLEEP - Case("lp ticker sleep test", lp_ticker_deepsleep_test), + Case("lp ticker sleep test", lp_ticker_deepsleep_test_setup_handler, lp_ticker_deepsleep_test, lp_ticker_deepsleep_test_teardown_handler), #endif Case("lp ticker glitch test", lp_ticker_glitch_test) }; diff --git a/TESTS/mbed_hal/sleep/main.cpp b/TESTS/mbed_hal/sleep/main.cpp index 5d64b8517be..46beb039b24 100644 --- a/TESTS/mbed_hal/sleep/main.cpp +++ b/TESTS/mbed_hal/sleep/main.cpp @@ -23,6 +23,7 @@ #include "utest/utest.h" #include "unity/unity.h" #include "greentea-client/test_env.h" +#include "mbed_lp_ticker_wrapper.h" #include "sleep_api_tests.h" @@ -40,7 +41,7 @@ * * 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 wait_ms() function for now. + * so we'll use the busy_wait_ms() function for now. */ #define SERIAL_FLUSH_TIME_MS 20 @@ -103,6 +104,20 @@ bool compare_timestamps(unsigned int delta_ticks, unsigned int ticker_width, uns } } +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(); @@ -125,17 +140,6 @@ void sleep_usticker_test() const ticker_irq_handler_type us_ticker_irq_handler_org = set_us_ticker_irq_handler(us_ticker_isr); - // call ticker_read_us to initialize ticker upper layer - // prevents subsequent scheduling of max_delta interrupt during ticker initialization while test execution - // (e.g when ticker_read_us is called) - ticker_read_us(ticker); -#ifdef DEVICE_LPTICKER - // call ticker_read_us to initialize lp_ticker - // prevents scheduling interrupt during ticker initialization (in lp_ticker_init) while test execution - // (e.g when ticker_read_us is called for lp_ticker, see MBED_CPU_STATS_ENABLED) - ticker_read_us(get_lp_ticker_data()); -#endif - /* Test only sleep functionality. */ sleep_manager_lock_deep_sleep(); TEST_ASSERT_FALSE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should be locked"); @@ -173,17 +177,12 @@ void deepsleep_lpticker_test() const unsigned int ticker_freq = ticker->interface->get_info()->frequency; const unsigned int ticker_width = ticker->interface->get_info()->bits; - // call ticker_read_us to initialize ticker upper layer - // prevents subsequent scheduling of max_delta interrupt during ticker initialization while test execution - // (e.g when ticker_read_us is called) - ticker_read_us(ticker); - const ticker_irq_handler_type lp_ticker_irq_handler_org = set_lp_ticker_irq_handler(lp_ticker_isr); /* Give some time Green Tea to finish UART transmission before entering * deep-sleep mode. */ - wait_ms(SERIAL_FLUSH_TIME_MS); + busy_wait_ms(SERIAL_FLUSH_TIME_MS); TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked"); @@ -218,7 +217,7 @@ void deepsleep_high_speed_clocks_turned_off_test() /* Give some time Green Tea to finish UART transmission before entering * deep-sleep mode. */ - wait_ms(SERIAL_FLUSH_TIME_MS); + busy_wait_ms(SERIAL_FLUSH_TIME_MS); TEST_ASSERT_TRUE_MESSAGE(sleep_manager_can_deep_sleep(), "deep sleep should not be locked"); @@ -256,15 +255,38 @@ utest::v1::status_t greentea_failure_handler(const Case *const source, const fai utest::v1::status_t greentea_test_setup(const size_t number_of_cases) { GREENTEA_SETUP(60, "default_auto"); + /* Suspend RTOS Kernel to enable sleep modes. */ + osKernelSuspend(); +#if DEVICE_LPTICKER + ticker_suspend(get_lp_ticker_data()); +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + lp_ticker_wrapper_suspend(); +#endif +#endif + ticker_suspend(get_us_ticker_data()); + us_ticker_init(); #if DEVICE_LPTICKER lp_ticker_init(); #endif - /* Suspend RTOS Kernel to enable sleep modes. */ - osKernelSuspend(); + return greentea_test_setup_handler(number_of_cases); } +void greentea_test_teardown(const size_t passed, const size_t failed, const failure_t failure) +{ + ticker_resume(get_us_ticker_data()); +#if DEVICE_LPTICKER +#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) + lp_ticker_wrapper_resume(); +#endif + ticker_resume(get_lp_ticker_data()); +#endif + osKernelResume(0); + + greentea_test_teardown_handler(passed, failed, failure); +} + Case cases[] = { Case("sleep - source of wake-up - us ticker", sleep_usticker_test, greentea_failure_handler), #if DEVICE_LPTICKER @@ -273,7 +295,7 @@ Case cases[] = { #endif }; -Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); +Specification specification(greentea_test_setup, cases, greentea_test_teardown); int main() { From 00b8e24446d2ad97d6c3678789202f92e5f44fe4 Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Wed, 8 Aug 2018 15:42:53 -0500 Subject: [PATCH 6/7] Fix rollover handling in ticker frequency test To handle timer rollovers the test tests-mbed_hal-common_tickers_freq calls intf->set_interrupt(0). For this to work correctly the ticker implementation must fire an interrupt on every rollover event though intf->set_interrupt(0) was called only once. Whether an interrupt will fire only once or multiple times is undefined behavior which cannot be relied upon. To avoid this undefined behavior this patch continually schedules an interrupt and performs overflow detection on every read. This also removes the possibility of race conditions due to overflowCounter incrementing at the wrong time. --- TESTS/mbed_hal/common_tickers_freq/main.cpp | 61 ++++++++++++++------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/TESTS/mbed_hal/common_tickers_freq/main.cpp b/TESTS/mbed_hal/common_tickers_freq/main.cpp index 564fe323c53..bf5c9a0301c 100644 --- a/TESTS/mbed_hal/common_tickers_freq/main.cpp +++ b/TESTS/mbed_hal/common_tickers_freq/main.cpp @@ -39,26 +39,51 @@ using namespace utest::v1; const ticker_interface_t *intf; +uint32_t intf_mask; +uint32_t intf_last_tick; +uint32_t intf_elapsed_ticks; ticker_irq_handler_type prev_handler; -static volatile unsigned int overflowCounter; - uint32_t ticks_to_us(uint32_t ticks, uint32_t freq) { return (uint32_t)((uint64_t)ticks * US_PER_S / freq); } -void ticker_event_handler_stub(const ticker_data_t *const ticker) +void elapsed_ticks_reset() { - if (ticker == get_us_ticker_data()) { - us_ticker_clear_interrupt(); - } else { -#if DEVICE_LPTICKER - lp_ticker_clear_interrupt(); -#endif - } + core_util_critical_section_enter(); + + const uint32_t ticker_bits = intf->get_info()->bits; - overflowCounter++; + intf_mask = (1 << ticker_bits) - 1; + intf_last_tick = intf->read(); + intf_elapsed_ticks = 0; + + core_util_critical_section_exit(); +} + +uint32_t elapsed_ticks_update() +{ + core_util_critical_section_enter(); + + const uint32_t current_tick = intf->read(); + intf_elapsed_ticks += (current_tick - intf_last_tick) & intf_mask; + intf_last_tick = current_tick; + + /* Schedule next interrupt half way to overflow */ + uint32_t next = (current_tick + intf_mask / 2) & intf_mask; + intf->set_interrupt(next); + + uint32_t elapsed = intf_elapsed_ticks; + + core_util_critical_section_exit(); + return elapsed; +} + +void ticker_event_handler_stub(const ticker_data_t *const ticker) +{ + intf->clear_interrupt(); + elapsed_ticks_update(); } /* Test that the ticker is operating at the frequency it specifies. */ @@ -68,13 +93,13 @@ void ticker_frequency_test() char _value[128] = { }; int expected_key = 1; const uint32_t ticker_freq = intf->get_info()->frequency; - const uint32_t ticker_bits = intf->get_info()->bits; - const uint32_t ticker_max = (1 << ticker_bits) - 1; intf->init(); + elapsed_ticks_reset(); + /* Detect overflow for tickers with lower counters width. */ - intf->set_interrupt(0); + elapsed_ticks_update(); greentea_send_kv("timing_drift_check_start", 0); @@ -84,9 +109,7 @@ void ticker_frequency_test() expected_key = strcmp(_key, "base_time"); } while (expected_key); - overflowCounter = 0; - - const uint32_t begin_ticks = intf->read(); + const uint32_t begin_ticks = elapsed_ticks_update(); /* Assume that there was no overflow at this point - we are just after init. */ greentea_send_kv(_key, ticks_to_us(begin_ticks, ticker_freq)); @@ -94,9 +117,9 @@ void ticker_frequency_test() /* Wait for 2nd signal from host. */ greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); - const uint32_t end_ticks = intf->read(); + const uint32_t end_ticks = elapsed_ticks_update(); - greentea_send_kv(_key, ticks_to_us(end_ticks + overflowCounter * ticker_max, ticker_freq)); + greentea_send_kv(_key, ticks_to_us(end_ticks, ticker_freq)); /* Get the results from host. */ greentea_parse_kv(_key, _value, sizeof(_key), sizeof(_value)); From dc2e2c0ce01c355422bc18d07a65df75542f30ad Mon Sep 17 00:00:00 2001 From: Russ Butler Date: Sat, 11 Aug 2018 15:45:43 -0500 Subject: [PATCH 7/7] Speed optimization for LowPowerTickerWrapper Only reschedule the Timeout object in the low power ticker wrapper if it is not already pending. --- hal/LowPowerTickerWrapper.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/hal/LowPowerTickerWrapper.cpp b/hal/LowPowerTickerWrapper.cpp index fdbdf2c01f4..70b5190d4bd 100644 --- a/hal/LowPowerTickerWrapper.cpp +++ b/hal/LowPowerTickerWrapper.cpp @@ -251,9 +251,14 @@ void LowPowerTickerWrapper::_schedule_match(timestamp_t current) if (!_set_interrupt_allowed) { // Can't use _intf->set_interrupt so use microsecond Timeout instead - uint32_t ticks = cycles_until_match < _min_count_until_match ? cycles_until_match : _min_count_until_match; - _timeout.attach_us(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks)); - _pending_timeout = true; + + // Speed optimization - if a timer has already been scheduled + // then don't schedule it again. + if (!_pending_timeout) { + uint32_t ticks = cycles_until_match < _min_count_until_match ? cycles_until_match : _min_count_until_match; + _timeout.attach_us(mbed::callback(this, &LowPowerTickerWrapper::_timeout_handler), _lp_ticks_to_us(ticks)); + _pending_timeout = true; + } return; }