From d56e938b939e05a35b23e9090883303e28ee7745 Mon Sep 17 00:00:00 2001
From: Daniele Cattaneo <daniele3.cattaneo@mail.polimi.it>
Date: Sat, 8 Apr 2023 21:25:07 +0200
Subject: [PATCH] Add semaphore synchronization primitive.

Signed-off-by: Terraneo Federico <fede.tft@miosix.org>
---
 miosix/kernel/kernel.h |   2 +
 miosix/kernel/sync.cpp | 113 ++++++++++++++++++++++++++++++++++++++++-
 miosix/kernel/sync.h   | 105 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 218 insertions(+), 2 deletions(-)

diff --git a/miosix/kernel/kernel.h b/miosix/kernel/kernel.h
index 59ee7b83..72b62990 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 90fce52b..90f58212 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 626846ae..972d0af7 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
+};
+
 /**
  * \}
  */
-- 
GitLab