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
pull/7524/head
Russ Butler 2018-07-12 11:25:07 -05:00
parent 472ababfef
commit adc64cccac
5 changed files with 702 additions and 121 deletions

View File

@ -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;
}
}

239
hal/LowPowerTickerWrapper.h Normal file
View File

@ -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
/** @}*/

View File

@ -14,11 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
#include "hal/lp_ticker_api.h" #include "hal/lp_ticker_api.h"
#include "hal/mbed_lp_ticker_wrapper.h"
#if DEVICE_LPTICKER #if DEVICE_LPTICKER
void lp_ticker_set_interrupt_wrapper(timestamp_t timestamp);
static ticker_event_queue_t events = { 0 }; static ticker_event_queue_t events = { 0 };
static ticker_irq_handler_type irq_handler = ticker_irq_handler; 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, .read = lp_ticker_read,
.disable_interrupt = lp_ticker_disable_interrupt, .disable_interrupt = lp_ticker_disable_interrupt,
.clear_interrupt = lp_ticker_clear_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, .set_interrupt = lp_ticker_set_interrupt,
#endif
.fire_interrupt = lp_ticker_fire_interrupt, .fire_interrupt = lp_ticker_fire_interrupt,
.get_info = lp_ticker_get_info, .get_info = lp_ticker_get_info,
.free = lp_ticker_free, .free = lp_ticker_free,
@ -45,7 +40,11 @@ static const ticker_data_t lp_data = {
const ticker_data_t *get_lp_ticker_data(void) 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; return &lp_data;
#endif
} }
ticker_irq_handler_type set_lp_ticker_irq_handler(ticker_irq_handler_type ticker_irq_handler) 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) void lp_ticker_irq_handler(void)
{ {
#if LPTICKER_DELAY_TICKS > 0
lp_ticker_wrapper_irq_handler(irq_handler);
#else
if (irq_handler) { if (irq_handler) {
irq_handler(&lp_data); irq_handler(&lp_data);
} }
#endif
} }
#endif #endif

View File

@ -13,144 +13,115 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
#include "hal/lp_ticker_api.h" #include "hal/mbed_lp_ticker_wrapper.h"
#if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0) #if DEVICE_LPTICKER && (LPTICKER_DELAY_TICKS > 0)
#include "Timeout.h" #include "hal/LowPowerTickerWrapper.h"
#include "mbed_critical.h" #include "platform/mbed_critical.h"
static const timestamp_t min_delta = LPTICKER_DELAY_TICKS;
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;
// Do not use SingletonPtr since this must be initialized in a critical section // Do not use SingletonPtr since this must be initialized in a critical section
static mbed::Timeout *timeout; static LowPowerTickerWrapper *ticker_wrapper;
static uint64_t timeout_data[sizeof(mbed::Timeout) / 8]; static uint64_t ticker_wrapper_data[(sizeof(LowPowerTickerWrapper) + 7) / 8];
static bool init = false;
/** static void lp_ticker_wrapper_init()
* Initialize variables
*/
static void init_local()
{ {
MBED_ASSERT(core_util_in_critical_section()); ticker_wrapper->init();
const ticker_info_t *info = lp_ticker_get_info();
if (info->bits >= 32) {
mask = 0xffffffff;
} else {
mask = ((uint64_t)1 << info->bits) - 1;
}
// Round us_per_tick up
timestamp_t us_per_tick = (1000000 + info->frequency - 1) / info->frequency;
// 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;
timeout = new (timeout_data) mbed::Timeout();
} }
/** static uint32_t lp_ticker_wrapper_read()
* 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)
{ {
MBED_ASSERT(core_util_in_critical_section()); return ticker_wrapper->read();
uint32_t delta = (timestamp - last_read) & mask;
if (delta < min_delta + 2) {
timestamp = (last_read + min_delta + 2) & mask;
}
lp_ticker_set_interrupt(timestamp);
} }
/** static void lp_ticker_wrapper_set_interrupt(timestamp_t timestamp)
* 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()
{ {
core_util_critical_section_enter(); ticker_wrapper->set_interrupt(timestamp);
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();
}
timeout_pending = false;
core_util_critical_section_exit();
} }
/** static void lp_ticker_wrapper_disable_interrupt()
* Wrapper around lp_ticker_set_interrupt to prevent blocking {
* ticker_wrapper->disable_interrupt();
* 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 static void lp_ticker_wrapper_clear_interrupt()
* {
* This wrapper function prevents lp_ticker_set_interrupt from being called ticker_wrapper->clear_interrupt();
* 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. static void lp_ticker_wrapper_fire_interrupt()
* {
* @param timestamp Time to call ticker irq ticker_wrapper->fire_interrupt();
* @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) static const ticker_info_t *lp_ticker_wrapper_get_info()
{
return ticker_wrapper->get_info();
}
static void lp_ticker_wrapper_free()
{
ticker_wrapper->free();
}
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(); core_util_critical_section_enter();
if (!init) { if (!init) {
init_local(); // Force ticker to initialize
init = true; get_lp_ticker_data();
} }
timestamp_t current = lp_ticker_read(); ticker_wrapper->irq_handler(handler);
if (pending) {
// Check if pending should be cleared
if (((current - last_set_interrupt) & mask) >= min_delta) {
pending = false;
}
}
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;
}
core_util_critical_section_exit(); core_util_critical_section_exit();
} }
const ticker_data_t *get_lp_ticker_wrapper_data(const ticker_data_t *data)
{
core_util_critical_section_enter();
if (!init) {
ticker_wrapper = new (ticker_wrapper_data) LowPowerTickerWrapper(data, &lp_interface, LPTICKER_DELAY_TICKS, LPTICKER_DELAY_TICKS);
init = true;
}
core_util_critical_section_exit();
return &ticker_wrapper->data;
}
void lp_ticker_wrapper_suspend()
{
if (!init) {
// Force ticker to initialize
get_lp_ticker_data();
}
ticker_wrapper->suspend();
}
void lp_ticker_wrapper_resume()
{
if (!init) {
// Force ticker to initialize
get_lp_ticker_data();
}
ticker_wrapper->resume();
}
#endif #endif

View File

@ -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 <stdbool.h>
#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
/** @}*/