From 4d3888c06a046220f43663ae45c823cfd11df563 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 2 Nov 2017 12:34:15 +0200 Subject: [PATCH 1/6] Add Kernel::get_ms_count Give C++ access to the RTOS's absolute timebase, reducing the need to run private Timers and similar. Allows wait_until functionality, and makes it easier to avoid time drift. Place it in a new header and namespace in case we want more kernel functions in future. Try to cover over the breaking API change potentially upcoming in CMSIS-RTOS 2.1.1, when it reduces the tick count from 64-bit to 32-bit. (See https://github.com/ARM-software/CMSIS_5/issues/277) Explicitly state that ticks are milliseconds in mbed OS, despite CMSIS RTOS 2 permitting different tick rates. See also https://github.com/ARMmbed/mbed-os/pull/3648 (wait_until for condition variables) and https://github.com/ARMmbed/mbed-os/issues/5378 (EventQueue should use RTOS tick count). --- rtos/Kernel.cpp | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ rtos/Kernel.h | 51 +++++++++++++++++++++++++++++++++++++++ rtos/rtos.h | 1 + 3 files changed, 115 insertions(+) create mode 100644 rtos/Kernel.cpp create mode 100644 rtos/Kernel.h diff --git a/rtos/Kernel.cpp b/rtos/Kernel.cpp new file mode 100644 index 0000000000..2562556cff --- /dev/null +++ b/rtos/Kernel.cpp @@ -0,0 +1,63 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "rtos/Kernel.h" + +#include "mbed.h" + +namespace rtos { + +uint64_t Kernel::get_ms_count() { + // CMSIS-RTOS 2.1.0 and 2.1.1 differ in the time type. We assume + // our header at least matches the implementation, so we don't try looking + // at the run-time version report. (There's no compile-time version report) + + // 2.1.0 uint64_t osKernelGetTickCount(void), not documented as callable from ISR (but RTX does allow) + // 2.1.1 uint32_t osKernelGetTickCount(void), callable from ISR + // 2.1.x who knows? We assume could go back to uint64_t + if (sizeof osKernelGetTickCount() == sizeof(uint64_t)) { + return osKernelGetTickCount(); + } else /* assume 32-bit */ { + // Based on suggestion in CMSIS-RTOS 2.1.1 docs, but with reentrancy + // protection for the tick memory. We use critical section rather than a + // mutex, as hopefully this method can be callable from interrupt later - + // only thing currently preventing it is that pre CMSIS RTOS 2.1.1, it's + // not defined as safe. + // We assume this is called multiple times per 32-bit wrap period (49 days). + static uint32_t tick_h, tick_l; + + core_util_critical_section_enter(); + // The 2.1.1 API says this is legal from an ISR - we assume this means + // it's also legal with interrupts disabled. RTX implementation kind + // of conflates the two. + uint32_t tick32 = osKernelGetTickCount(); + if (tick32 < tick_l) { + tick_h++; + } + tick_l = tick32; + uint64_t ret = ((uint64_t) tick_h << 32) | tick_l; + core_util_critical_section_exit(); + return ret; + } +} + +} diff --git a/rtos/Kernel.h b/rtos/Kernel.h new file mode 100644 index 0000000000..8be9a2abe1 --- /dev/null +++ b/rtos/Kernel.h @@ -0,0 +1,51 @@ +/* mbed Microcontroller Library + * Copyright (c) 2017 ARM Limited + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef KERNEL_H +#define KERNEL_H + +#include + +namespace rtos { +/** \addtogroup rtos */ +/** @{*/ + +/** Functions in the Kernel namespace control RTOS kernel information. */ +namespace Kernel { + +/** Read the current RTOS kernel millisecond tick count. + The tick count corresponds to the tick count used by the RTOS for timing + purposes. It increments monotonically from 0 at boot, hence effectively + never wraps. If the underlying RTOS only provides a 32-bit tick count, + this method expands it to 64 bits. + @return RTOS kernel current tick count + @note mbed OS always uses millisecond RTOS ticks, and this could only wrap + after half a billion years + @note You cannot call this function from ISR context. + */ +uint64_t get_ms_count(); + +} // namespace Kernel + +} // namespace rtos +#endif + +/** @}*/ diff --git a/rtos/rtos.h b/rtos/rtos.h index 0856dd408a..4b3802cd23 100644 --- a/rtos/rtos.h +++ b/rtos/rtos.h @@ -26,6 +26,7 @@ #define RTOS_H #include "mbed_rtos_storage.h" +#include "rtos/Kernel.h" #include "rtos/Thread.h" #include "rtos/Mutex.h" #include "rtos/RtosTimer.h" From 99dc805e79ca0ec12e1d6631050986eb57d97dad Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Fri, 10 Nov 2017 17:49:00 +0200 Subject: [PATCH 2/6] Add Thread::wait_until API is somewhat loose to cope with potential shift in the underlying RTOS APIs. --- rtos/Thread.cpp | 29 +++++++++++++++++++++++++++++ rtos/Thread.h | 21 ++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/rtos/Thread.cpp b/rtos/Thread.cpp index e773e63d84..70e20e3d12 100644 --- a/rtos/Thread.cpp +++ b/rtos/Thread.cpp @@ -353,6 +353,35 @@ osStatus Thread::wait(uint32_t millisec) { return osDelay(millisec); } +osStatus Thread::wait_until(uint64_t millisec) { + // CMSIS-RTOS 2.1.0 and 2.1.1 differ in the time type, which we determine + // by looking at the return type of osKernelGetTickCount. We assume + // our header at least matches the implementation, so we don't try looking + // at the run-time version report. (There's no compile-time version report) + if (sizeof osKernelGetTickCount() == sizeof(uint64_t)) { + // CMSIS-RTOS 2.1.0 has a 64-bit API. The corresponding RTX 5.2.0 can't + // delay more than 0xfffffffe ticks, but there's no limit stated for + // the generic API. + return osDelayUntil(millisec); + } else { + // 64-bit time doesn't wrap (for half a billion years, at last) + uint64_t now = Kernel::get_ms_count(); + // Report being late on entry + if (now >= millisec) { + return osErrorParameter; + } + // We're about to make a 32-bit delay call, so have at least this limit + if (millisec - now > 0xFFFFFFFF) { + return osErrorParameter; + } + // And this may have its own internal limit - we'll find out. + // We hope/assume there's no problem with passing + // osWaitForever = 0xFFFFFFFF - that value is only specified to have + // special meaning for osSomethingWait calls. + return osDelay(millisec - now); + } +} + osStatus Thread::yield() { return osThreadYield(); } diff --git a/rtos/Thread.h b/rtos/Thread.h index 9f82ec0ed0..ce245dcc63 100644 --- a/rtos/Thread.h +++ b/rtos/Thread.h @@ -360,7 +360,10 @@ public: */ static osEvent signal_wait(int32_t signals, uint32_t millisec=osWaitForever); - /** Wait for a specified time period in millisec: + /** Wait for a specified time period in milliseconds + Being tick-based, the delay will be up to the specified time - eg for + a value of 1 the system waits until the next millisecond tick occurs, + leading to a delay of 0-1 milliseconds. @param millisec time delay value @return status code that indicates the execution status of the function. @@ -368,6 +371,22 @@ public: */ static osStatus wait(uint32_t millisec); + /** Wait until a specified time in millisec + The specified time is according to Kernel::get_ms_count(). + @param millisec absolute time in millisec + @return status code that indicates the execution status of the function. + @note not callable from interrupt + @note if millisec is equal to or lower than the current tick count, this + returns immediately, either with an error or "osOK". + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + delay is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + it may return with an immediate error, or wait for the maximum delay. + + @note You cannot call this function from ISR context. + */ + static osStatus wait_until(uint64_t millisec); + /** Pass control to next thread that is in state READY. @return status code that indicates the execution status of the function. From 3bb2c445ca59fb8ffc65a322cfe703af775bf529 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Thu, 18 Jan 2018 10:23:41 +0200 Subject: [PATCH 3/6] Correct ConditionVariable ISR comments No ConditionVariable methods are useable from ISR, due to the built-in mutex and the requirement that the caller of notify holds the mutex. --- rtos/ConditionVariable.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rtos/ConditionVariable.h b/rtos/ConditionVariable.h index c69722d897..662992f53c 100644 --- a/rtos/ConditionVariable.h +++ b/rtos/ConditionVariable.h @@ -120,7 +120,7 @@ class ConditionVariable : private mbed::NonCopyable { public: /** Create and Initialize a ConditionVariable object * - * @note You may call this function from ISR context. + * @note You cannot call this function from ISR context. */ ConditionVariable(Mutex &mutex); @@ -190,7 +190,7 @@ public: * * @note - The thread calling this function must be the owner of the ConditionVariable's mutex * - * @note This function may be called from ISR context. + * @note You cannot call this function from ISR context. */ void notify_one(); @@ -198,13 +198,13 @@ public: * * @note - The thread calling this function must be the owner of the ConditionVariable's mutex * - * @note This function may be called from ISR context. + * @note You cannot call this function from ISR context. */ void notify_all(); /** ConditionVariable destructor * - * @note You may call this function from ISR context. + * @note You cannot call this function from ISR context. */ ~ConditionVariable(); From bbefeb44323fc567b23a8b99d97dbeabb02305f2 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Fri, 10 Nov 2017 15:01:51 +0200 Subject: [PATCH 4/6] Add ConditionVariable::wait_until Given the 64-bit timebase, add wait_until to ConditionVariable. Move the timeout example to wait_until(), and give wait_for() an alternative example, as it's no longer the best option for a timeout. Tidy up - remove the redundant RESUME_SIGNAL definition. --- rtos/ConditionVariable.cpp | 20 ++++++++++++++++- rtos/ConditionVariable.h | 46 +++++++++++++++++++++++++++++++------- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/rtos/ConditionVariable.cpp b/rtos/ConditionVariable.cpp index 69be38b936..b54fe3a126 100644 --- a/rtos/ConditionVariable.cpp +++ b/rtos/ConditionVariable.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ #include "rtos/ConditionVariable.h" +#include "rtos/Kernel.h" #include "rtos/Thread.h" #include "mbed_error.h" @@ -27,7 +28,6 @@ namespace rtos { - ConditionVariable::Waiter::Waiter(): sem(0), prev(NULL), next(NULL), in_list(false) { // No initialization to do @@ -64,6 +64,24 @@ bool ConditionVariable::wait_for(uint32_t millisec) return timeout; } +bool ConditionVariable::wait_until(uint64_t millisec) +{ + uint64_t now = Kernel::get_ms_count(); + + if (now >= millisec) { + // Time has already passed - standard behaviour is to + // treat as a "try". + return wait_for(0); + } else if (millisec - now >= osWaitForever) { + // Exceeds maximum delay of underlying wait_for - + // spuriously wake after 49 days, indicating no timeout. + wait_for(osWaitForever - 1); + return false; + } else { + return wait_for(millisec - now); + } +} + void ConditionVariable::notify_one() { MBED_ASSERT(_mutex.get_owner() == Thread::gettid()); diff --git a/rtos/ConditionVariable.h b/rtos/ConditionVariable.h index 662992f53c..1a1c4576ea 100644 --- a/rtos/ConditionVariable.h +++ b/rtos/ConditionVariable.h @@ -150,6 +150,39 @@ public: */ void wait(); + /** Wait for a notification until specified time + * + * @param millisec absolute end time referenced to Kernel::get_ms_count() + * @return true if a timeout occurred, false otherwise. + * + * @note - The thread calling this function must be the owner of the + * ConditionVariable's mutex and it must be locked exactly once + * @note - Spurious notifications can occur so the caller of this API + * should check to make sure the condition they are waiting on has + * been met + * + * Example: + * @code + * mutex.lock(); + * uint64_t end_time = Kernel::get_ms_count() + COND_WAIT_TIMEOUT; + * + * while (!condition_met) { + * if (cond.wait_until(end_time)) { + * break; + * } + * } + * + * if (condition_met) { + * function_to_handle_condition(); + * } + * + * mutex.unlock(); + * @endcode + * + * @note You cannot call this function from ISR context. + */ + bool wait_until(uint64_t millisec); + /** Wait for a notification or timeout * * @param millisec timeout value or osWaitForever in case of no time-out. @@ -164,15 +197,12 @@ public: * Example: * @code * mutex.lock(); - * Timer timer; - * timer.start(); * - * bool timed_out = false; - * uint32_t time_left = TIMEOUT; - * while (!condition_met && !timed_out) { - * timed_out = cond.wait_for(time_left); - * uint32_t elapsed = timer.read_ms(); - * time_left = elapsed > TIMEOUT ? 0 : TIMEOUT - elapsed; + * while (!condition_met) { + * cond.wait_for(MAX_SLEEP_TIME); + * if (!condition_met) { + * do_other_work_while_condition_false(); + * } * } * * if (condition_met) { From cd573a603fb35f974d5421cbf33cf90e8c0f5ed2 Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Fri, 10 Nov 2017 15:01:51 +0200 Subject: [PATCH 5/6] Add Mutex::trylock_until and trylock_for Given the 64-bit timebase, add trylock_until to Mutex. Naming is based on a combination of Mutex::trylock, Thread::wait_until, and C++11 timed_mutex::try_lock_until. pthreads and C11 use "timedlock", but that's not a good fit against our existing trylock() and lock(timeout) - they have only absolute-time waits, not relative. To increase the similarity to C++11, add trylock_for - same parameters as lock, but with the bool return value of trylock and trylock_until. Add an assertion when convering status codes to booleans to check that there are no non-timeout errors. --- rtos/Mutex.cpp | 26 +++++++++++++++++++++++--- rtos/Mutex.h | 29 +++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/rtos/Mutex.cpp b/rtos/Mutex.cpp index 44fefa0759..edc2cdcb52 100644 --- a/rtos/Mutex.cpp +++ b/rtos/Mutex.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ #include "rtos/Mutex.h" +#include "rtos/Kernel.h" #include #include "mbed_error.h" @@ -58,11 +59,30 @@ osStatus Mutex::lock(uint32_t millisec) { } bool Mutex::trylock() { - if (osMutexAcquire(_id, 0) == osOK) { - _count++; + return trylock_for(0); +} + +bool Mutex::trylock_for(uint32_t millisec) { + osStatus status = lock(millisec); + if (status == osOK) { return true; + } + + MBED_ASSERT(status == osErrorTimeout || status == osErrorResource); + + return false; +} + +bool Mutex::trylock_until(uint64_t millisec) { + uint64_t now = Kernel::get_ms_count(); + + if (now >= millisec) { + return trylock(); + } else if (millisec - now >= osWaitForever) { + // API permits early return + return trylock_for(osWaitForever - 1); } else { - return false; + return trylock_for(millisec - now); } } diff --git a/rtos/Mutex.h b/rtos/Mutex.h index 4273d14722..acd035e173 100644 --- a/rtos/Mutex.h +++ b/rtos/Mutex.h @@ -78,11 +78,36 @@ public: /** Try to lock the mutex, and return immediately @return true if the mutex was acquired, false otherwise. + @note equivalent to trylock_for(0) - @note This function cannot be called from ISR context. + @note You cannot call this function from ISR context. */ bool trylock(); + /** Try to lock the mutex for a specified time + @param millisec timeout value or 0 in case of no time-out. + @return true if the mutex was acquired, false otherwise. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the lock attempt will time out earlier than specified. + + @note You cannot call this function from ISR context. + */ + bool trylock_for(uint32_t millisec); + + /** Try to lock the mutex until specified time + @param millisec absolute timeout time, referenced to Kernel::get_ms_count() + @return true if the mutex was acquired, false otherwise. + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the lock attempt will time out earlier than specified. + + @note You cannot call this function from ISR context. + */ + bool trylock_until(uint64_t millisec); + /** Unlock the mutex that has previously been locked by the same thread @return status code that indicates the execution status of the function: @a osOK the mutex has been released. @@ -90,7 +115,7 @@ public: @a osErrorResource the mutex was not locked or the current thread wasn't the owner. @a osErrorISR this function cannot be called from the interrupt service routine. - @note This function cannot be called from ISR context. + @note You cannot call this function from ISR context. */ osStatus unlock(); From 2b77caa32c9c34e4c048c569aee5739a387e658b Mon Sep 17 00:00:00 2001 From: Kevin Bracey Date: Fri, 10 Nov 2017 15:01:51 +0200 Subject: [PATCH 6/6] Add Semaphore::wait_until Given the 64-bit timebase, add wait_until to Semaphore. Naming is based on Thread::wait_until. pthreads uses "timedwait", but that's not a good fit against our existing wait() - pthreads only has an absolute-time wait, not relative. --- rtos/Semaphore.cpp | 15 +++++++++++++++ rtos/Semaphore.h | 12 ++++++++++++ 2 files changed, 27 insertions(+) diff --git a/rtos/Semaphore.cpp b/rtos/Semaphore.cpp index fb082d3c44..3da622d905 100644 --- a/rtos/Semaphore.cpp +++ b/rtos/Semaphore.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ #include "rtos/Semaphore.h" +#include "rtos/Kernel.h" #include "platform/mbed_assert.h" #include @@ -57,6 +58,20 @@ int32_t Semaphore::wait(uint32_t millisec) { } } +int32_t Semaphore::wait_until(uint64_t millisec) { + uint64_t now = Kernel::get_ms_count(); + uint32_t timeout; + + if (now >= millisec) { + return wait(0); + } else if (millisec - now >= osWaitForever) { + // API permits early return + return wait(osWaitForever - 1); + } else { + return wait(millisec - now); + } +} + osStatus Semaphore::release(void) { return osSemaphoreRelease(_id); } diff --git a/rtos/Semaphore.h b/rtos/Semaphore.h index e8fc22205a..4584792290 100644 --- a/rtos/Semaphore.h +++ b/rtos/Semaphore.h @@ -67,6 +67,18 @@ public: */ int32_t wait(uint32_t millisec=osWaitForever); + /** Wait until a Semaphore resource becomes available. + @param millisec absolute timeout time, referenced to Kernel::get_ms_count() + @return number of available tokens, before taking one; or -1 in case of incorrect parameters + @note the underlying RTOS may have a limit to the maximum wait time + due to internal 32-bit computations, but this is guaranteed to work if the + wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded, + the acquire attempt will time out earlier than specified. + + @note You cannot call this function from ISR context. + */ + int32_t wait_until(uint64_t millisec); + /** Release a Semaphore resource that was obtain with Semaphore::wait. @return status code that indicates the execution status of the function: @a osOK the token has been correctly released.