Correct core RTOS sleep routine timing

Chrono conversions inadvertantly changed the core timed sleep routine
used by the RTOS idle to use `OsTimer::update_and_get_tick()` instead of
`OsTimer::get_tick()`.

Correct this, and expand/clarify documentation and naming to try to
prevent recurrence.

Another minor fix observed while inspecting code - `OsClock` can't just
use `milliseconds`, it should match the period of `OsTimer`, which
theoretically can be different.
pull/12938/head
Kevin Bracey 2020-05-07 11:39:35 +03:00
parent 029109a2f0
commit 3f67eed31c
3 changed files with 52 additions and 13 deletions

View File

@ -200,16 +200,18 @@ OsClock::time_point do_timed_sleep_absolute(OsClock::time_point wake_time, bool
#if MBED_CONF_RTOS_PRESENT
/* The 32-bit limit is part of the API - we will always wake within 2^32 ticks */
/* This version is tuned for RTOS use, where the RTOS needs to know the time spent sleeping */
OsClock::duration_u32 do_timed_sleep_relative(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *), void *wake_predicate_handle)
/* Note that unlike do_timed_sleep_relative_or_forever it does not do a tick update on entry; */
/* it assumes the caller has been using the ticker, and is passing a delay relative to the */
/* time point of the ticks it has acknowledged. */
OsClock::duration_u32 do_timed_sleep_relative_to_acknowledged_ticks(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *), void *wake_predicate_handle)
{
OsClock::time_point sleep_start = OsClock::now();
// When running with RTOS, the requested delay will be based on the kernel's tick count.
// If it missed a tick as entering idle, we should reflect that by moving the
// start time back to reflect its current idea of time.
// Example: OS tick count = 100, our tick count = 101, requested delay = 50
// If it missed a tick as entering idle, we should reflect that by basing the start time
// on its current idea of time - OsClock::acknowledged_ticks().
// Example: OS acknowledged tick count = 100, our reported tick count = 101, requested delay = 50
// We need to schedule wake for tick 150, report 50 ticks back to our caller, and
// clear the unacknowledged tick count.
sleep_start -= os_timer->unacknowledged_ticks();
OsClock::time_point sleep_start = OsClock::acknowledged_ticks();
OsClock::time_point sleep_finish = do_timed_sleep_absolute(sleep_start + wake_delay, wake_predicate, wake_predicate_handle);
@ -226,7 +228,8 @@ void do_untimed_sleep(bool (*wake_predicate)(void *), void *wake_predicate_handl
}
/* max() delay is treated as "wait forever" */
/* This version is tuned for non-RTOS use, where we don't need to return sleep time, and waiting forever is possible */
/* This version is tuned for non-RTOS use, where we must update the tick count, */
/* don't need to return sleep time, and waiting forever is possible */
void do_timed_sleep_relative_or_forever(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *), void *wake_predicate_handle)
{
// Special-case 0 delay, to save multiple callers having to do it. Just call the predicate once.

View File

@ -42,18 +42,42 @@ extern OsTimer *os_timer;
OsTimer *init_os_timer();
/** A C++11 chrono TrivialClock for os_timer
*
* Due to the nature of OsTimer/SysTimer, this does not have a single `now` method, but has
* multiple ways to report the current state:
*
* High-res timeline -------------------------------------------------------------
* Ticks | a | b | b | b | c | c | c | c | c | d ^
* ^ ^ ^ os_timer->get_time()
* acknowledged_ticks() reported_ticks() now()
*
* (a) is time read from hardware by OsTimer, reported to the user of OsTimer, and acknowledged by that user.
* (b) is time read from hardware by OsTimer, reported to the user of OsTimer, but not yet acknowledged.
* (c) is time already elapsed in the hardware but yet to be read and processed as ticks by OsTimer.
* (d) is time already elapsed in the hardware that doesn't yet form a tick.
*
* Time is "reported" either by:
* * calls to the OsTimer's handler following start_tick - these must be acknowledged
* * the result of OsTimer::update_and_get_tick() / OsClock::now() - calling this implies acknowledgment.
*
* As such `now()` is used when the ticker is not in use - it processes ticks that would have been
* processed by the tick handler. If the ticker is in uses `reported_ticks` or `acknowleged_ticks` must be used.
*
* @note To fit better into the chrono framework, OsClock uses
* chrono::milliseconds as its representation, which makes it signed
* and at least 45 bits (so it will be int64_t or equivalent).
* and at least 45 bits, so it will be int64_t or equivalent, unlike
* OsTimer which uses uint64_t rep.
*/
struct OsClock {
/* Standard TrivialClock fields */
using duration = std::chrono::milliseconds;
using rep = duration::rep;
using period = duration::period;
using period = OsTimer::period;
using rep = std::chrono::milliseconds::rep;
using duration = std::chrono::duration<rep, period>; // == std::chrono::milliseconds, if period is std::milli
using time_point = std::chrono::time_point<OsClock, duration>;
static constexpr bool is_steady = true;
// Read the hardware, and return the updated time_point.
// Initialize the timing system if necessary - this could be the first call.
// See SysTimer::update_and_get_tick for more details.
static time_point now()
{
// We are a real Clock with a well-defined epoch. As such we distinguish ourselves
@ -61,6 +85,18 @@ struct OsClock {
// are not convertible, so need to fiddle here.
return time_point(init_os_timer()->update_and_get_tick().time_since_epoch());
}
// Return the current reported tick count, without update.
// Assumes timer has already been initialized, as ticker should have been in use to
// keep that tick count up-to-date. See SysTimer::get_tick for more details.
static time_point reported_ticks()
{
return time_point(os_timer->get_tick().time_since_epoch());
}
// Return the acknowledged tick count.
static time_point acknowledged_ticks()
{
return reported_ticks() - os_timer->unacknowledged_ticks();
}
// Slightly-optimised variant of OsClock::now() that assumes os_timer is initialised.
static time_point now_with_init_done()
{
@ -81,7 +117,7 @@ OsClock::time_point do_timed_sleep_absolute(OsClock::time_point wake_time, bool
#if MBED_CONF_RTOS_PRESENT
/* Maximum sleep time is 2^32-1 ticks; timer is always set to achieve this */
/* Assumes that ticker has been in use prior to call, so restricted to RTOS use */
OsClock::duration_u32 do_timed_sleep_relative(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *) = NULL, void *wake_predicate_handle = NULL);
OsClock::duration_u32 do_timed_sleep_relative_to_acknowledged_ticks(OsClock::duration_u32 wake_delay, bool (*wake_predicate)(void *) = NULL, void *wake_predicate_handle = NULL);
#else
void do_untimed_sleep(bool (*wake_predicate)(void *), void *wake_predicate_handle = NULL);

View File

@ -139,7 +139,7 @@ extern "C" {
rtos::Kernel::Clock::duration_u32 ticks_to_sleep{osKernelSuspend()};
// osKernelSuspend will call OS_Tick_Disable, cancelling the tick, which frees
// up the os timer for the timed sleep
rtos::Kernel::Clock::duration_u32 ticks_slept = mbed::internal::do_timed_sleep_relative(ticks_to_sleep, rtos_event_pending);
rtos::Kernel::Clock::duration_u32 ticks_slept = mbed::internal::do_timed_sleep_relative_to_acknowledged_ticks(ticks_to_sleep, rtos_event_pending);
MBED_ASSERT(ticks_slept < rtos::Kernel::wait_for_u32_max);
osKernelResume(ticks_slept.count());
}