From b33426395e7930837aca2a231f5c24095977ac06 Mon Sep 17 00:00:00 2001 From: Terraneo Federico <fede.tft@miosix.org> Date: Tue, 25 Apr 2023 23:58:52 +0200 Subject: [PATCH] Implement timedWait at the kernel level. Also provide unlocking wait primitives. --- miosix/kernel/kernel.cpp | 113 +++++++++++------ miosix/kernel/kernel.h | 257 ++++++++++++++++++++++++++++++--------- miosix/kernel/sync.cpp | 207 ++++++++----------------------- miosix/kernel/sync.h | 9 -- 4 files changed, 325 insertions(+), 261 deletions(-) diff --git a/miosix/kernel/kernel.cpp b/miosix/kernel/kernel.cpp index a575212a..c5ac40df 100755 --- a/miosix/kernel/kernel.cpp +++ b/miosix/kernel/kernel.cpp @@ -260,13 +260,10 @@ bool isKernelRunning() * Used by Thread::sleep() and pthread_cond_timedwait() to add a thread to * sleeping list. The list is sorted by the wakeupTime field to reduce time * required to wake threads during context switch. - * Also sets thread SLEEP_FLAG. It is labeled IRQ not because it is meant to be - * used inside an IRQ, but because interrupts must be disabled prior to calling - * this function. + * Interrupts must be disabled prior to calling this function. */ -void IRQaddToSleepingList(SleepData *x) +static void IRQaddToSleepingList(SleepData *x) { - x->thread->flags.IRQsetSleep(true); if(sleepingList.empty() || sleepingList.front()->wakeupTime>=x->wakeupTime) { sleepingList.push_front(x); @@ -277,19 +274,6 @@ void IRQaddToSleepingList(SleepData *x) } } -/** - * \internal - * Used by pthread_cond_timedwait() to remove a thread from sleeping list in case that it - * is woke up by a signal or broadcast. - * It is labeled IRQ not because it is meant to be - * used inside an IRQ, but because interrupts must be disabled prior to calling - * this function. - */ -void IRQremoveFromSleepingList(SleepData *x) -{ - sleepingList.removeFast(x); -} - /** * \internal * Called to check if it's time to wake some thread. @@ -307,9 +291,8 @@ bool IRQwakeThreads(long long currentTime) for(auto it=sleepingList.begin();it!=sleepingList.end();) { if(currentTime<(*it)->wakeupTime) break; - (*it)->thread->flags.IRQsetSleep(false); //Wake thread - //Reset cond wait flag to wakeup threads in pthread_cond_timedwait() too - (*it)->thread->flags.IRQsetCondWait(false); + //Wake both threads doing absoluteSleep() and timedWait() + (*it)->thread->flags.IRQexitSleepAndWait(); if(const_cast<Thread*>(runningThread)->getPriority()<(*it)->thread->getPriority()) result=true; it=sleepingList.erase(it); @@ -396,7 +379,8 @@ void Thread::nanoSleepUntil(long long absoluteTimeNs) //the timer isr will wake threads, modifying the sleepingList { FastInterruptDisableLock lock; - IRQaddToSleepingList(&d);//Also sets SLEEP_FLAG + d.thread->flags.IRQsetSleep(true); //Sleeping thread: set sleep flag + IRQaddToSleepingList(&d); } // NOTE: There is no need to synchronize the timer (calling IRQsetNextInterrupt) // with the list at this point. Because, Thread::yield will make a supervisor @@ -422,6 +406,32 @@ void Thread::IRQwait() const_cast<Thread*>(runningThread)->flags.IRQsetWait(true); } +void Thread::PKrestartKernelAndWait(PauseKernelLock& dLock) +{ + (void)dLock; + //Implemented by upgrading the lock to an interrupt disable one + FastInterruptDisableLock dLockIrq; + auto savedNesting=kernelRunning; + kernelRunning=0; + IRQenableIrqAndWaitImpl(); + if(kernelRunning!=0) errorHandler(UNEXPECTED); + kernelRunning=savedNesting; +} + +TimedWaitResult Thread::PKrestartKernelAndTimedWait(PauseKernelLock& dLock, + long long absoluteTimeNs) +{ + (void)dLock; + //Implemented by upgrading the lock to an interrupt disable one + FastInterruptDisableLock dLockIrq; + auto savedNesting=kernelRunning; + kernelRunning=0; + auto result=IRQenableIrqAndTimedWaitImpl(absoluteTimeNs); + if(kernelRunning!=0) errorHandler(UNEXPECTED); + kernelRunning=savedNesting; + return result; +} + void Thread::wakeup() { //pausing the kernel is not enough because of IRQwait and IRQwakeup @@ -446,18 +456,14 @@ void Thread::IRQwakeup() this->flags.IRQsetWait(false); } -Thread *Thread::getCurrentThread() -{ - Thread *result=const_cast<Thread*>(runningThread); - if(result) return result; - //This function must always return a pointer to a valid thread. The first - //time this is called before the kernel is started, however, runningThread - //is nullptr, thus we allocate the idle thread and return a pointer to that. - return allocateIdleThread(); -} - Thread *Thread::IRQgetCurrentThread() { + //NOTE: this code is currently safe to be called either with interrupt + //enabed or not, and with the kernel paused or not, as well as before the + //kernel is started, so getCurrentThread() and PKgetCurrentThread() all + //directly call here. If introducing changes that break this property, these + //three functions may need to be split + //Implementation is the same as getCurrentThread, but to keep a consistent //interface this method is duplicated Thread *result=const_cast<Thread*>(runningThread); @@ -823,6 +829,37 @@ void Thread::threadLauncher(void *(*threadfunc)(void*), void *argv) errorHandler(UNEXPECTED); } +void Thread::IRQenableIrqAndWaitImpl() +{ + const_cast<Thread*>(runningThread)->flags.IRQsetWait(true); + auto savedNesting=interruptDisableNesting; //For InterruptDisableLock + interruptDisableNesting=0; + miosix_private::doEnableInterrupts(); + Thread::yield(); //Here the wait becomes effective + miosix_private::doDisableInterrupts(); + if(interruptDisableNesting!=0) errorHandler(UNEXPECTED); + interruptDisableNesting=savedNesting; +} + +TimedWaitResult Thread::IRQenableIrqAndTimedWaitImpl(long long absoluteTimeNs) +{ + absoluteTimeNs=std::max(absoluteTimeNs,100000LL); + Thread *t=const_cast<Thread*>(runningThread); + SleepData sleepData(t,absoluteTimeNs); + t->flags.IRQsetWait(true); //timedWait thread: set wait flag + IRQaddToSleepingList(&sleepData); + auto savedNesting=interruptDisableNesting; //For InterruptDisableLock + interruptDisableNesting=0; + miosix_private::doEnableInterrupts(); + Thread::yield(); //Here the wait becomes effective + miosix_private::doDisableInterrupts(); + if(interruptDisableNesting!=0) errorHandler(UNEXPECTED); + interruptDisableNesting=savedNesting; + bool removed=sleepingList.removeFast(&sleepData); + //If the thread was still in the sleeping list, it was woken up by a wakeup() + return removed ? TimedWaitResult::NoTimeout : TimedWaitResult::Timeout; +} + Thread *Thread::allocateIdleThread() { //NOTE: this function is only called once before the kernel is started, so @@ -852,21 +889,21 @@ void Thread::ThreadFlags::IRQsetWait(bool waiting) Scheduler::IRQwaitStatusHook(this->t); } -void Thread::ThreadFlags::IRQsetJoinWait(bool waiting) +void Thread::ThreadFlags::IRQsetSleep(bool sleeping) { - if(waiting) flags |= WAIT_JOIN; else flags &= ~WAIT_JOIN; + if(sleeping) flags |= SLEEP; else flags &= ~SLEEP; Scheduler::IRQwaitStatusHook(this->t); } -void Thread::ThreadFlags::IRQsetCondWait(bool waiting) +void Thread::ThreadFlags::IRQexitSleepAndWait() { - if(waiting) flags |= WAIT_COND; else flags &= ~WAIT_COND; + flags &= ~(WAIT | SLEEP); Scheduler::IRQwaitStatusHook(this->t); } -void Thread::ThreadFlags::IRQsetSleep(bool sleeping) +void Thread::ThreadFlags::IRQsetJoinWait(bool waiting) { - if(sleeping) flags |= SLEEP; else flags &= ~SLEEP; + if(waiting) flags |= WAIT_JOIN; else flags &= ~WAIT_JOIN; Scheduler::IRQwaitStatusHook(this->t); } diff --git a/miosix/kernel/kernel.h b/miosix/kernel/kernel.h index 342811e0..793dd4bb 100755 --- a/miosix/kernel/kernel.h +++ b/miosix/kernel/kernel.h @@ -412,6 +412,15 @@ long long getTime() noexcept; */ long long IRQgetTime() noexcept; +/** + * Possible return values of timedWait + */ +enum class TimedWaitResult +{ + NoTimeout, + Timeout +}; + //Forwrd declaration class SleepData; class MemoryProfiling; @@ -541,16 +550,19 @@ public: static void nanoSleepUntil(long long absoluteTimeNs); /** - * This method stops the thread until another thread calls wakeup() on this - * thread.<br>Calls to wait are not cumulative. If wait() is called two - * times, only one call to wakeup() is needed to wake the thread. - * <br>CANNOT be called when the kernel is paused. + * This method stops the thread until wakeup() is called. + * Ths method is useful to implement any kind of blocking primitive, + * including device drivers. + * + * CANNOT be called when the kernel is paused. */ static void wait(); /** - * Same as wait(), but is meant to be used only inside an IRQ or when - * interrupts are disabled.<br> + * This method stops the thread until wakeup() is called. + * Ths method is useful to implement any kind of blocking primitive, + * including device drivers. + * * Note: this method is meant to put the current thread in wait status in a * piece of code where interrupts are disbled; it returns immediately, so * the user is responsible for re-enabling interrupts and calling yield to @@ -559,13 +571,148 @@ public: * \code * disableInterrupts(); * ... - * Thread::IRQwait();//Return immediately + * Thread::IRQwait(); //Return immediately * enableInterrupts(); - * Thread::yield();//After this, thread is in wait status + * Thread::yield(); //After this, thread is in wait status * \endcode + * + * Consider using IRQenableIrqAndWait() instead. */ static void IRQwait(); + /** + * This method stops the thread until wakeup() is called. + * Ths method is useful to implement any kind of blocking primitive, + * including device drivers. + * + * NOTE: this method is meant to put the current thread in wait status in a + * piece of code where the kernel is paused (preemption disabled). + * Preemption will be enabled during the waiting period, and disabled back + * before this method returns. + * + * \param dLock the PauseKernelLock object that was used to disable + * preemption in the current context. + */ + static void PKrestartKernelAndWait(PauseKernelLock& dLock); + + /** + * This method stops the thread until wakeup() is called. + * Ths method is useful to implement any kind of blocking primitive, + * including device drivers. + * + * NOTE: this method is meant to put the current thread in wait status in a + * piece of code where interrupts are disbled, interrupts will be enabled + * during the waiting period, and disabled back before this method returns. + * + * \param dLock the InterruptDisableLock object that was used to disable + * interrupts in the current context. + */ + static void IRQenableIrqAndWait(InterruptDisableLock& dLock) + { + (void)dLock; //Common implementation doesn't need it + return IRQenableIrqAndWaitImpl(); + } + + /** + * This method stops the thread until wakeup() is called. + * Ths method is useful to implement any kind of blocking primitive, + * including device drivers. + * + * NOTE: this method is meant to put the current thread in wait status in a + * piece of code where interrupts are disbled, interrupts will be enabled + * during the waiting period, and disabled back before this method returns. + * + * \param dLock the FastInterruptDisableLock object that was used to disable + * interrupts in the current context. + */ + static void IRQenableIrqAndWait(FastInterruptDisableLock& dLock) + { + (void)dLock; //Common implementation doesn't need it + return IRQenableIrqAndWaitImpl(); + } + + /** + * This method stops the thread until wakeup() is called or the specified + * absolute time in nanoseconds is reached. + * Ths method is thus a combined IRQwait() and absoluteSleep(), and is + * useful to implement any kind of blocking primitive with timeout, + * including device drivers. + * + * \param absoluteTimeoutNs absolute time after which the wait times out + * \return TimedWaitResult::Timeout if the wait timed out + */ + static TimedWaitResult timedWait(long long absoluteTimeNs) + { + FastInterruptDisableLock dLock; + return IRQenableIrqAndTimedWaitImpl(absoluteTimeNs); + } + + /** + * This method stops the thread until wakeup() is called or the specified + * absolute time in nanoseconds is reached. + * Ths method is thus a combined IRQwait() and absoluteSleep(), and is + * useful to implement any kind of blocking primitive with timeout, + * including device drivers. + * + * NOTE: this method is meant to put the current thread in wait status in a + * piece of code where the kernel is paused (preemption disabled). + * Preemption will be enabled during the waiting period, and disabled back + * before this method returns. + * + * \param dLock the PauseKernelLock object that was used to disable + * preemption in the current context. + * \param absoluteTimeoutNs absolute time after which the wait times out + * \return TimedWaitResult::Timeout if the wait timed out + */ + static TimedWaitResult PKrestartKernelAndTimedWait(PauseKernelLock& dLock, + long long absoluteTimeNs); + + /** + * This method stops the thread until wakeup() is called or the specified + * absolute time in nanoseconds is reached. + * Ths method is thus a combined IRQwait() and absoluteSleep(), and is + * useful to implement any kind of blocking primitive with timeout, + * including device drivers. + * + * NOTE: this method is meant to put the current thread in wait status in a + * piece of code where interrupts are disbled, interrupts will be enabled + * during the waiting period, and disabled back before this method returns. + * + * \param dLock the InterruptDisableLock object that was used to disable + * interrupts in the current context. + * \param absoluteTimeoutNs absolute time after which the wait times out + * \return TimedWaitResult::Timeout if the wait timed out + */ + static TimedWaitResult IRQenableIrqAndTimedWait(InterruptDisableLock& dLock, + long long absoluteTimeNs) + { + (void)dLock; //Common implementation doesn't need it + return IRQenableIrqAndTimedWaitImpl(absoluteTimeNs); + } + + /** + * This method stops the thread until wakeup() is called or the specified + * absolute time in nanoseconds is reached. + * Ths method is thus a combined IRQwait() and absoluteSleep(), and is + * useful to implement any kind of blocking primitive with timeout, + * including device drivers. + * + * NOTE: this method is meant to put the current thread in wait status in a + * piece of code where interrupts are disbled, interrupts will be enabled + * during the waiting period, and disabled back before this method returns. + * + * \param dLock the FastInterruptDisableLock object that was used to disable + * interrupts in the current context. + * \param absoluteTimeoutNs absolute time after which the wait times out + * \return TimedWaitResult::Timeout if the wait timed out + */ + static TimedWaitResult IRQenableIrqAndTimedWait(FastInterruptDisableLock& dLock, + long long absoluteTimeNs) + { + (void)dLock; //Common implementation doesn't need it + return IRQenableIrqAndTimedWaitImpl(absoluteTimeNs); + } + /** * Wakeup a thread. * <br>CANNOT be called when the kernel is paused. @@ -574,28 +721,42 @@ public: /** * Wakeup a thread. - * <br>Can be called when the kernel is paused. + * <br>Can only be called when the kernel is paused. */ void PKwakeup(); /** - * Same as wakeup(), but is meant to be used only inside an IRQ or when - * interrupts are disabled. + * Wakeup a thread. + * <br>Can only be called inside an IRQ or when interrupts are disabled. */ void IRQwakeup(); /** - * Return a pointer to the Thread class of the current thread. * \return a pointer to the current thread. * - * Can be called when the kernel is paused. * Returns a valid pointer also if called before the kernel is started. */ - static Thread *getCurrentThread(); + static Thread *getCurrentThread() + { + //Safe to call without disabling IRQ, see implementation + return IRQgetCurrentThread(); + } /** - * Same as get_current_thread(), but meant to be used insida an IRQ, when - * interrupts are disabled or when the kernel is paused. + * \return a pointer to the current thread. + * + * Returns a valid pointer also if called before the kernel is started. + */ + static Thread *PKgetCurrentThread() + { + //Safe to call without disabling IRQ, see implementation + return IRQgetCurrentThread(); + } + + /** + * \return a pointer to the current thread. + * + * Returns a valid pointer also if called before the kernel is started. */ static Thread *IRQgetCurrentThread(); @@ -775,25 +936,25 @@ private: void IRQsetWait(bool waiting); /** - * Set the wait_join flag of the thread. + * Set the sleep flag of the thread. * Can only be called with interrupts disabled or within an interrupt. - * \param waiting if true the flag will be set, otherwise cleared + * \param sleeping if true the flag will be set, otherwise cleared */ - void IRQsetJoinWait(bool waiting); + void IRQsetSleep(bool sleeping); /** - * Set wait_cond flag of the thread. - * Can only be called with interrupts disabled or within an interrupt. - * \param waiting if true the flag will be set, otherwise cleared + * Shorthand for IRQsetWait(false); IRQsetSleep(false); + * Used by IRQwakeThreads to wake both threads doing absoluteSleep() + * and timedWait() */ - void IRQsetCondWait(bool waiting); + void IRQexitSleepAndWait(); /** - * Set the sleep flag of the thread. + * Set the wait_join flag of the thread. * Can only be called with interrupts disabled or within an interrupt. - * \param sleeping if true the flag will be set, otherwise cleared + * \param waiting if true the flag will be set, otherwise cleared */ - void IRQsetSleep(bool sleeping); + void IRQsetJoinWait(bool waiting); /** * Set the deleted flag of the thread. This flag can't be cleared. @@ -858,7 +1019,7 @@ private: /** * \return true if the thread is in the ready status */ - bool isReady() const { return (flags & 0x67)==0; } + bool isReady() const { return (flags & 0x27)==0; } /** * \return true if the thread is detached @@ -869,11 +1030,6 @@ private: * \return true if the thread is waiting a join */ bool isWaitingJoin() const { return flags & WAIT_JOIN; } - - /** - * \return true if the thread is waiting on a condition variable - */ - bool isWaitingCond() const { return flags & WAIT_COND; } /** * \return true if the thread is running unprivileged inside a process. @@ -903,14 +1059,11 @@ private: ///\internal Thread is waiting for a join static const unsigned int WAIT_JOIN=1<<5; - - ///\internal Thread is waiting on a condition variable - static const unsigned int WAIT_COND=1<<6; ///\internal Thread is running in userspace - static const unsigned int USERSPACE=1<<7; + static const unsigned int USERSPACE=1<<6; - unsigned short flags;///<\internal flags are stored here + unsigned char flags;///<\internal flags are stored here }; #ifdef WITH_PROCESSES @@ -985,6 +1138,16 @@ private: */ static void threadLauncher(void *(*threadfunc)(void*), void *argv); + /** + * Common implementation of all IRQenableIrqAndWait calls + */ + static void IRQenableIrqAndWaitImpl(); + + /** + * Common implementation of all timedWait calls + */ + static TimedWaitResult IRQenableIrqAndTimedWaitImpl(long long absoluteTimeNs); + /** * Allocates the idle thread and makes cur point to it * Can only be called before the kernel is started, is called exactly once @@ -1039,14 +1202,8 @@ private: #endif //WITH_CPU_TIME_COUNTER //friend functions - //Need access to status - friend void IRQaddToSleepingList(SleepData *); - //Need access to status - friend void IRQremoveFromSleepingList(SleepData *); - //Needs access to status + //Needs access to flags friend bool IRQwakeThreads(long long); - //Needs access to watermark, status, next - friend void *idleThread(void *); //Needs to create the idle thread friend void startKernel(); //Needs threadLauncher @@ -1054,24 +1211,12 @@ private: unsigned int *, void *); //Needs access to priority, savedPriority, mutexLocked and flags. friend class Mutex; - //Needs access to flags - friend class ConditionVariable; - //Needs access to flags - friend class Semaphore; //Needs access to flags, schedData friend class PriorityScheduler; //Needs access to flags, schedData friend class ControlScheduler; //Needs access to flags, schedData friend class EDFScheduler; - //Needs access to flags - friend int ::pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *); - //Needs access to flags - friend int pthreadCondTimedWaitImpl(pthread_cond_t *, pthread_mutex_t *, long long); - //Needs access to flags - friend int ::pthread_cond_signal(pthread_cond_t *); - //Needs access to flags - friend int ::pthread_cond_broadcast(pthread_cond_t *); //Needs access to cppReent friend class CppReentrancyAccessor; #ifdef WITH_PROCESSES diff --git a/miosix/kernel/sync.cpp b/miosix/kernel/sync.cpp index 2a74c7ba..5d66ed60 100644 --- a/miosix/kernel/sync.cpp +++ b/miosix/kernel/sync.cpp @@ -36,9 +36,6 @@ using namespace std; namespace miosix { -void IRQaddToSleepingList(SleepData *x); -void IRQremoveFromSleepingList(SleepData *x); - // // class Mutex // @@ -50,7 +47,7 @@ Mutex::Mutex(Options opt): owner(nullptr), next(nullptr), waiting() void Mutex::PKlock(PauseKernelLock& dLock) { - Thread *p=Thread::getCurrentThread(); + Thread *p=Thread::PKgetCurrentThread(); if(owner==nullptr) { owner=p; @@ -118,7 +115,7 @@ void Mutex::PKlock(PauseKernelLock& dLock) void Mutex::PKlockToDepth(PauseKernelLock& dLock, unsigned int depth) { - Thread *p=Thread::getCurrentThread(); + Thread *p=Thread::PKgetCurrentThread(); if(owner==nullptr) { owner=p; @@ -188,7 +185,7 @@ void Mutex::PKlockToDepth(PauseKernelLock& dLock, unsigned int depth) bool Mutex::PKtryLock(PauseKernelLock& dLock) { - Thread *p=Thread::getCurrentThread(); + Thread *p=Thread::PKgetCurrentThread(); if(owner==nullptr) { owner=p; @@ -210,7 +207,7 @@ bool Mutex::PKtryLock(PauseKernelLock& dLock) bool Mutex::PKunlock(PauseKernelLock& dLock) { - Thread *p=Thread::getCurrentThread(); + Thread *p=Thread::PKgetCurrentThread(); if(owner!=p) return false; if(recursiveDepth>0) @@ -288,7 +285,7 @@ bool Mutex::PKunlock(PauseKernelLock& dLock) unsigned int Mutex::PKunlockAllDepthLevels(PauseKernelLock& dLock) { - Thread *p=Thread::getCurrentThread(); + Thread *p=Thread::PKgetCurrentThread(); if(owner!=p) return 0; //Remove this mutex from the list of mutexes locked by the owner @@ -324,8 +321,8 @@ unsigned int Mutex::PKunlockAllDepthLevels(PauseKernelLock& dLock) while(walk!=nullptr) { if(walk->waiting.empty()==false) - if (pr.mutexLessOp(walk->waiting.front()->getPriority())) - pr = walk->waiting.front()->getPriority(); + if(pr.mutexLessOp(walk->waiting.front()->getPriority())) + pr=walk->waiting.front()->getPriority(); walk=walk->next; } if(pr!=owner->getPriority()) Scheduler::PKsetPriority(owner,pr); @@ -370,154 +367,74 @@ static_assert(sizeof(ConditionVariable)==sizeof(pthread_cond_t),""); void ConditionVariable::wait(Mutex& m) { + WaitToken listItem(Thread::getCurrentThread()); PauseKernelLock dLock; - Thread *t=Thread::getCurrentThread(); - WaitToken listItem(t); - condList.push_back(&listItem); //Add entry to tail of list - - //Unlock mutex and wait - { - FastInterruptDisableLock l; - t->flags.IRQsetCondWait(true); - } - unsigned int depth=m.PKunlockAllDepthLevels(dLock); - { - RestartKernelLock eLock(dLock); - Thread::yield(); //Here the wait becomes effective - } + condList.push_back(&listItem); //Putting this thread last on the list (lifo policy) + Thread::PKrestartKernelAndWait(dLock); + condList.removeFast(&listItem); //In case of timeout or spurious wakeup m.PKlockToDepth(dLock,depth); } void ConditionVariable::wait(pthread_mutex_t *m) { + WaitToken listItem(Thread::getCurrentThread()); FastInterruptDisableLock dLock; - Thread *t=Thread::IRQgetCurrentThread(); - WaitToken listItem(t); - condList.push_back(&listItem); //Putting this thread last on the list (lifo policy) - t->flags.IRQsetCondWait(true); - unsigned int depth=IRQdoMutexUnlockAllDepthLevels(m); - { - FastInterruptEnableLock eLock(dLock); - Thread::yield(); //Here the wait becomes effective - } + condList.push_back(&listItem); //Putting this thread last on the list (lifo policy) + Thread::IRQenableIrqAndWait(dLock); + condList.removeFast(&listItem); //In case of spurious wakeup IRQdoMutexLockToDepth(m,dLock,depth); } TimedWaitResult ConditionVariable::timedWait(Mutex& m, long long absTime) { - //Disallow absolute sleeps with negative or too low values, as the ns2tick() - //algorithm in TimeConversion can't handle negative values and may undeflow - //even with very low values due to a negative adjustOffsetNs. As an unlikely - //side effect, very short sleeps done very early at boot will be extended. - absTime=std::max(absTime,100000LL); - + WaitToken listItem(Thread::getCurrentThread()); PauseKernelLock dLock; - Thread *t=Thread::getCurrentThread(); - WaitToken listItem(t); - condList.push_back(&listItem); //Add entry to tail of list - SleepData sleepData(t,absTime); - { - FastInterruptDisableLock l; - IRQaddToSleepingList(&sleepData); //Putting this thread on the sleeping list too - t->flags.IRQsetCondWait(true); - } - - //Unlock mutex and wait unsigned int depth=m.PKunlockAllDepthLevels(dLock); - { - RestartKernelLock eLock(dLock); - Thread::yield(); //Here the wait becomes effective - } - - //Ensure that the thread is removed from both list, as it can be woken by - //either a signal/broadcast (that removes it from condList) or by - //IRQwakeThreads (that removes it from sleeping list). - bool removed=condList.removeFast(&listItem); - { - FastInterruptDisableLock l; - IRQremoveFromSleepingList(&sleepData); - } - + condList.push_back(&listItem); //Putting this thread last on the list (lifo policy) + auto result=Thread::PKrestartKernelAndTimedWait(dLock,absTime); + condList.removeFast(&listItem); //In case of timeout or spurious wakeup m.PKlockToDepth(dLock,depth); - - //If the thread was still in the cond variable list, it was woken up by a timeout - return removed ? TimedWaitResult::Timeout : TimedWaitResult::NoTimeout; + return result; } TimedWaitResult ConditionVariable::timedWait(pthread_mutex_t *m, long long absTime) { - //Disallow absolute sleeps with negative or too low values, as the ns2tick() - //algorithm in TimeConversion can't handle negative values and may undeflow - //even with very low values due to a negative adjustOffsetNs. As an unlikely - //side effect, very short sleeps done very early at boot will be extended. - absTime=std::max(absTime,100000LL); + WaitToken listItem(Thread::getCurrentThread()); FastInterruptDisableLock dLock; - Thread *t=Thread::IRQgetCurrentThread(); - WaitToken listItem(t); - condList.push_back(&listItem); //Putting this thread last on the list (lifo policy) - SleepData sleepData(t,absTime); - IRQaddToSleepingList(&sleepData); //Putting this thread on the sleeping list too - t->flags.IRQsetCondWait(true); - unsigned int depth=IRQdoMutexUnlockAllDepthLevels(m); - { - FastInterruptEnableLock eLock(dLock); - Thread::yield(); //Here the wait becomes effective - } - //Ensure that the thread is removed from both list, as it can be woken by - //either a signal/broadcast (that removes it from condList) or by - //IRQwakeThreads (that removes it from sleeping list). - bool removed=condList.removeFast(&listItem); - IRQremoveFromSleepingList(&sleepData); - + condList.push_back(&listItem); //Putting this thread last on the list (lifo policy) + auto result=Thread::IRQenableIrqAndTimedWait(dLock,absTime); + condList.removeFast(&listItem); //In case of timeout or spurious wakeup IRQdoMutexLockToDepth(m,dLock,depth); - - //If the thread was still in the cond variable list, it was woken up by a timeout - return removed ? TimedWaitResult::Timeout : TimedWaitResult::NoTimeout; + return result; } bool ConditionVariable::doSignal() { bool hppw=false; - { - //Using interruptDisableLock because we need to call IRQsetCondWait - //that can only be called with irq disabled, othrwise we would use - //PauseKernelLock - FastInterruptDisableLock lock; - if(condList.empty()) return false; - //Remove from list and wakeup - Thread *t=condList.front()->thread; - condList.pop_front(); - t->flags.IRQsetCondWait(false); - t->flags.IRQsetSleep(false); //Needed due to timedwait - //Check for priority issues - if(t->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) - hppw=true; - } + FastInterruptDisableLock lock; //TODO: Can we pause kernel here? + if(condList.empty()) return false; + Thread *t=condList.front()->thread; + condList.pop_front(); + t->IRQwakeup(); + if(t->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + hppw=true; return hppw; } bool ConditionVariable::doBroadcast() { bool hppw=false; + FastInterruptDisableLock lock; //TODO: Can we pause kernel here? + while(!condList.empty()) { - //Using interruptDisableLock because we need to call IRQsetCondWait - //that can only be called with irq disabled, othrwise we would use - //PauseKernelLock - FastInterruptDisableLock lock; - while(!condList.empty()) - { - //Remove from list and wakeup - Thread *t=condList.front()->thread; - condList.pop_front(); - t->flags.IRQsetCondWait(false); - t->flags.IRQsetSleep(false); //Needed due to timedwait - //Check for priority issues - if(t->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) - hppw=true; - } + Thread *t=condList.front()->thread; + condList.pop_front(); + t->IRQwakeup(); + if(t->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + hppw=true; } return hppw; } @@ -538,8 +455,7 @@ Thread *Semaphore::IRQsignalNoPreempt() WaitToken *cd=fifo.front(); Thread *t=cd->thread; fifo.pop_front(); - t->flags.IRQsetCondWait(false); - t->flags.IRQsetSleep(false); //Needed due to timedwait + t->IRQwakeup(); return t; } @@ -582,25 +498,14 @@ void Semaphore::wait() return; } //Otherwise put ourselves in queue and wait - Thread *t=Thread::getCurrentThread(); - WaitToken listItem(t); + WaitToken listItem(Thread::IRQgetCurrentThread()); fifo.push_back(&listItem); //Add entry to tail of list - t->flags.IRQsetCondWait(true); - { - FastInterruptEnableLock eLock(dLock); - //The wait becomes effective here - Thread::yield(); - } + Thread::IRQenableIrqAndWait(dLock); + fifo.removeFast(&listItem); //In case of spurious wakeup } TimedWaitResult Semaphore::timedWait(long long absTime) { - //Disallow absolute sleeps with negative or too low values, as the ns2tick() - //algorithm in TimeConversion can't handle negative values and may undeflow - //even with very low values due to a negative adjustOffsetNs. As an unlikely - //side effect, very short sleeps done very early at boot will be extended. - absTime=std::max(absTime,100000LL); - //Global interrupt lock because Semaphore is IRQ-safe FastInterruptDisableLock dLock; //If the counter is positive, decrement it and we're done @@ -609,26 +514,12 @@ TimedWaitResult Semaphore::timedWait(long long absTime) count--; return TimedWaitResult::NoTimeout; } - //Otherwise put ourselves in queue... - Thread *t=Thread::getCurrentThread(); - WaitToken listItem(t); - fifo.push_back(&listItem); - //...and simultaneously to sleep - SleepData sleepData(t,absTime); - IRQaddToSleepingList(&sleepData); - t->flags.IRQsetCondWait(true); - { - FastInterruptEnableLock eLock(dLock); - //Wait/sleep becomes effective here - Thread::yield(); - } - - //We got woken up by either the sleep or the wait. Ensure that the thread - //is removed from both the wait list and the sleep list. - bool removed=fifo.removeFast(&listItem); - IRQremoveFromSleepingList(&sleepData); - //If we were still in the fifo, we were woken up by a timeout - return removed ? TimedWaitResult::Timeout : TimedWaitResult::NoTimeout; + //Otherwise put ourselves in queue and wait + WaitToken listItem(Thread::IRQgetCurrentThread()); + fifo.push_back(&listItem); //Add entry to tail of list + auto result=Thread::IRQenableIrqAndTimedWait(dLock,absTime); + fifo.removeFast(&listItem); //In case of timeout or spurious wakeup + return result; } } //namespace miosix diff --git a/miosix/kernel/sync.h b/miosix/kernel/sync.h index 9ee9a5f0..bc991312 100644 --- a/miosix/kernel/sync.h +++ b/miosix/kernel/sync.h @@ -398,15 +398,6 @@ public: Thread *thread; ///<\internal Thread that is waiting }; -/** - * Possible return values of timedWait - */ -enum class TimedWaitResult -{ - NoTimeout, - Timeout -}; - /** * A condition variable class for thread synchronization, available from * Miosix 1.53.<br> -- GitLab