diff --git a/miosix/kernel/kernel.h b/miosix/kernel/kernel.h index 59ee7b83d9ba7aa4576f379de989052379bb4ccb..72b6299073b0a92408ede30b63d2da9a0ec13441 100755 --- a/miosix/kernel/kernel.h +++ b/miosix/kernel/kernel.h @@ -1052,6 +1052,8 @@ private: 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 diff --git a/miosix/kernel/sync.cpp b/miosix/kernel/sync.cpp index 90fce52b554928acde73ed63044d9a7763a41faa..90f58212b8a66c3be46d36da52eae8d70c79d78e 100644 --- a/miosix/kernel/sync.cpp +++ b/miosix/kernel/sync.cpp @@ -410,7 +410,7 @@ 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 shor sleeps done very early at boot will be extended. + //side effect, very short sleeps done very early at boot will be extended. absTime=std::max(absTime,100000LL); PauseKernelLock dLock; @@ -451,7 +451,7 @@ TimedWaitResult ConditionVariable::timedWait(pthread_mutex_t *m, long long absTi //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 shor sleeps done very early at boot will be extended. + //side effect, very short sleeps done very early at boot will be extended. absTime=std::max(absTime,100000LL); FastInterruptDisableLock dLock; Thread *t=Thread::IRQgetCurrentThread(); @@ -525,4 +525,113 @@ void ConditionVariable::broadcast() if(hppw) Thread::yield(); } +// +// class Semaphore +// + +Thread *Semaphore::IRQsignalNoPreempt() +{ + //Check if somebody is waiting + if(fifo.empty()) + { + //Nobody there, just increment the counter + count++; + return nullptr; + } + CondData *cd=fifo.front(); + Thread *t=cd->thread; + fifo.pop_front(); + t->flags.IRQsetCondWait(false); + t->flags.IRQsetSleep(false); //Needed due to timedwait + return t; +} + +void Semaphore::IRQsignal() +{ + //Update the state of the FIFO and the counter + Thread *t=IRQsignalNoPreempt(); + if(t==nullptr) return; + //If the woken thread has higher priority trigger a reschedule + if(Thread::IRQgetCurrentThread()->IRQgetPriority()<t->IRQgetPriority()) + Scheduler::IRQfindNextThread(); +} + +void Semaphore::signal() +{ + bool hppw=false; + { + //Global interrupt lock because Semaphore is IRQ-safe + FastInterruptDisableLock dLock; + //Update the state of the FIFO and the counter + Thread *t=IRQsignalNoPreempt(); + if(t) + { + //If the woken thread has higher priority trigger a yield + if(Thread::IRQgetCurrentThread()->IRQgetPriority()<t->IRQgetPriority()) + hppw=true; + } + } + if(hppw) Thread::yield(); +} + +void Semaphore::wait() +{ + //Global interrupt lock because Semaphore is IRQ-safe + FastInterruptDisableLock dLock; + //If the counter is positive, decrement it and we're done + if(count>0) + { + count--; + return; + } + //Otherwise put ourselves in queue and wait + Thread *t=Thread::getCurrentThread(); + CondData listItem(t); + fifo.push_back(&listItem); //Add entry to tail of list + t->flags.IRQsetCondWait(true); + { + FastInterruptEnableLock eLock(dLock); + //The wait becomes effective here + Thread::yield(); + } +} + +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 + if(count>0) + { + count--; + return TimedWaitResult::NoTimeout; + } + //Otherwise put ourselves in queue... + Thread *t=Thread::getCurrentThread(); + CondData 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; +} + } //namespace miosix diff --git a/miosix/kernel/sync.h b/miosix/kernel/sync.h index 626846ae62e88bd4b88bb4bf18695e1face01401..972d0af7b19b3a243f243739705a677e0766f554 100644 --- a/miosix/kernel/sync.h +++ b/miosix/kernel/sync.h @@ -1,5 +1,6 @@ /*************************************************************************** * Copyright (C) 2008-2023 by Terraneo Federico * + * Copyright (C) 2023 by Daniele Cattaneo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -532,6 +533,110 @@ private: IntrusiveList<CondData> condList; }; +/** + * Semaphore primitive for syncronization between multiple threads and + * optionally an interrupt handler. + * + * A semaphore is an integer counter that represents the availability of one + * or more items of a resource. A producer thread can signal the semaphore to + * increment the counter, making more items available. A consumer thread can + * wait for the availability of at least one item (i.e. for the counter to be + * positive) by performing a `wait' on the semaphore. If the counter is already + * positive, wait decrements the counter and terminates immediately. Otherwise + * it waits for a `signal' to increment the counter first. + * + * It is possible to use Semaphores to orchestrate communication between IRQ + * handlers and the main driver code by using the APIs prefixed by `IRQ'. + * + * \note As with all other synchronization primitives, Semaphores are inherently + * shared between multiple threads, therefore special care must be taken in + * managing their lifetime and ownership. + * \since Miosix 2.5 + */ +class Semaphore +{ +public: + /** + * Initialize a new semaphore. + * \param initialCount The initial value of the counter. + */ + Semaphore(unsigned int initialCount=0) : count(initialCount) {} + + /** + * Increment the semaphore counter, waking up at most one waiting thread. + * Only for use in IRQ handlers. + * \warning Use in a thread context with interrupts disabled or with the + * kernel paused is forbidden. + */ + void IRQsignal(); + + /** + * Increment the semaphore counter, waking up at most one waiting thread. + */ + void signal(); + + /** + * Wait for the semaphore counter to be positive, and then decrement it. + */ + void wait(); + + /** + * Wait up to a given timeout for the semaphore counter to be positive, + * and then decrement it. + * \param absTime absolute timeout time in nanoseconds + * \return whether the return was due to a timeout or wakeup + */ + TimedWaitResult timedWait(long long absTime); + + /** + * Decrement the counter only if it is positive. Only for use in IRQ + * handlers or with interrupts disabled. + * \return true if the counter was positive. + */ + inline bool IRQtryWait() + { + // Check if the counter is positive + if(count>0) + { + // The wait "succeeded" + count--; + return true; + } + return false; + } + + /** + * Decrement the counter only if it is positive. + * \return true if the counter was positive. + */ + bool tryWait() + { + // Global interrupt lock because Semaphore is IRQ-safe + FastInterruptDisableLock dLock; + return IRQtryWait(); + } + + /** + * \return the current semaphore counter. + */ + unsigned int getCount() { return count; } + +private: + // Disallow copies + Semaphore(const Semaphore&) = delete; + Semaphore& operator= (const Semaphore&) = delete; + + /** + * \internal + * Internal method that signals the semaphore without triggering a + * rescheduling for prioritizing newly-woken threads. + */ + inline Thread *IRQsignalNoPreempt(); + + volatile unsigned int count; ///< Counter of the semaphore + IntrusiveList<CondData> fifo; ///< List of waiting threads +}; + /** * \} */