kopia lustrzana https://github.com/OpenRTX/OpenRTX
555 wiersze
17 KiB
C++
555 wiersze
17 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2014 by Terraneo Federico *
|
|
* *
|
|
* 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 *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* As a special exception, if other files instantiate templates or use *
|
|
* macros or inline functions from this file, or you compile this file *
|
|
* and link it with other works to produce a work based on this file, *
|
|
* this file does not by itself cause the resulting work to be covered *
|
|
* by the GNU General Public License. However the source code for this *
|
|
* file must still be made available in accordance with the GNU General *
|
|
* Public License. This exception does not invalidate any other reasons *
|
|
* why a work based on this file might be covered by the GNU General *
|
|
* Public License. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/> *
|
|
***************************************************************************/
|
|
|
|
#ifndef QUEUE_H
|
|
#define QUEUE_H
|
|
|
|
#include "kernel.h"
|
|
#include "error.h"
|
|
|
|
namespace miosix {
|
|
|
|
/**
|
|
* \addtogroup Sync
|
|
* \{
|
|
*/
|
|
|
|
/**
|
|
* A queue, used to transfer data between TWO threads, or between ONE thread
|
|
* and an IRQ.<br>
|
|
* If you need to tranfer data between more than two threads, you need to
|
|
* use mutexes to ensure that only one thread at a time calls get, and only one
|
|
* thread at a time calls put.<br>
|
|
* Dynamically creating a queue with new or on the stack must be done with care,
|
|
* to avoid deleting a queue with a waiting thread, and to avoid situations
|
|
* where a thread tries to access a deleted queue.
|
|
* \tparam T the type of elements in the queue
|
|
* \tparam len the length of the Queue. Value 0 is forbidden
|
|
*/
|
|
template <typename T, unsigned int len>
|
|
class Queue
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor, create a new empty queue.
|
|
*/
|
|
Queue() : waiting(0), numElem(0), putPos(0), getPos(0) {}
|
|
|
|
/**
|
|
* \return true if the queue is empty
|
|
*/
|
|
bool isEmpty() const { return numElem==0; }
|
|
|
|
/**
|
|
* \return true if the queue is full
|
|
*/
|
|
bool isFull() const { return numElem==len; }
|
|
|
|
/**
|
|
* \return the number of elements currently in the queue
|
|
*/
|
|
unsigned int size() const { return numElem; }
|
|
|
|
/**
|
|
* \return the maximum number of elements the queue can hold
|
|
*/
|
|
unsigned int capacity() const { return len; }
|
|
|
|
/**
|
|
* If a queue is empty, waits until the queue is not empty.
|
|
*/
|
|
void waitUntilNotEmpty();
|
|
|
|
/**
|
|
* If a queue is full, waits until the queue is not full
|
|
*/
|
|
void waitUntilNotFull();
|
|
|
|
/**
|
|
* Get an element from the queue. If the queue is empty, then sleep until
|
|
* an element becomes available.
|
|
* \param elem an element from the queue
|
|
*/
|
|
void get(T& elem);
|
|
|
|
/**
|
|
* Put an element to the queue. If the queue is full, then sleep until a
|
|
* place becomes available.
|
|
* \param elem element to add to the queue
|
|
*/
|
|
void put(const T& elem);
|
|
|
|
/**
|
|
* Get an element from the queue, only if the queue is not empty.<br>
|
|
* Can ONLY be used inside an IRQ, or when interrupts are disabled.
|
|
* \param elem an element from the queue. The element is valid only if the
|
|
* return value is true
|
|
* \return true if the queue was not empty
|
|
*/
|
|
bool IRQget(T& elem);
|
|
|
|
/**
|
|
* Get an element from the queue, only if the queue is not empty.<br>
|
|
* Can ONLY be used inside an IRQ, or when interrupts are disabled.
|
|
* \param elem an element from the queue. The element is valid only if the
|
|
* return value is true
|
|
* \param hppw is not modified if no thread is woken or if the woken thread
|
|
* has a lower or equal priority than the currently running thread, else is
|
|
* set to true
|
|
* \return true if the queue was not empty
|
|
*/
|
|
bool IRQget(T& elem, bool& hppw);
|
|
|
|
/**
|
|
* Put an element to the queue, only if th queue is not full.<br>
|
|
* Can ONLY be used inside an IRQ, or when interrupts are disabled.
|
|
* \param elem element to add. The element has been added only if the
|
|
* return value is true
|
|
* \return true if the queue was not full.
|
|
*/
|
|
bool IRQput(const T& elem);
|
|
|
|
/**
|
|
* Put an element to the queue, only if th queue is not full.<br>
|
|
* Can ONLY be used inside an IRQ, or when interrupts are disabled.
|
|
* \param elem element to add. The element has been added only if the
|
|
* return value is true
|
|
* \param hppw is not modified if no thread is woken or if the woken thread
|
|
* has a lower or equal priority than the currently running thread, else is
|
|
* set to true
|
|
* \return true if the queue was not full.
|
|
*/
|
|
bool IRQput(const T& elem, bool& hppw);
|
|
|
|
/**
|
|
* Clear all items in the queue.<br>
|
|
* Cannot be used inside an IRQ
|
|
*/
|
|
void reset()
|
|
{
|
|
FastInterruptDisableLock lock;
|
|
IRQreset();
|
|
}
|
|
|
|
/**
|
|
* Same as reset(), but to be used only inside IRQs or when interrupts are
|
|
* disabled.
|
|
*/
|
|
void IRQreset()
|
|
{
|
|
IRQwakeWaitingThread();
|
|
putPos=getPos=numElem=0;
|
|
}
|
|
|
|
private:
|
|
//Unwanted methods
|
|
Queue(const Queue& s);///< No public copy constructor
|
|
Queue& operator = (const Queue& s);///< No publc operator =
|
|
|
|
/**
|
|
* Wake an eventual waiting thread.
|
|
* Must be called when interrupts are disabled
|
|
*/
|
|
void IRQwakeWaitingThread()
|
|
{
|
|
if(!waiting) return;
|
|
waiting->IRQwakeup();//Wakeup eventual waiting thread
|
|
waiting=0;
|
|
}
|
|
|
|
//Queue data
|
|
T buffer[len];///< queued elements are put here. Used as a ring buffer
|
|
Thread *waiting;///< If not null holds the thread waiting
|
|
volatile unsigned int numElem;///< nuber of elements in the queue
|
|
volatile unsigned int putPos; ///< index of buffer where to get next element
|
|
volatile unsigned int getPos; ///< index of buffer where to put next element
|
|
};
|
|
|
|
template <typename T, unsigned int len>
|
|
void Queue<T,len>::waitUntilNotEmpty()
|
|
{
|
|
FastInterruptDisableLock dLock;
|
|
IRQwakeWaitingThread();
|
|
while(isEmpty())
|
|
{
|
|
waiting=Thread::IRQgetCurrentThread();
|
|
Thread::IRQwait();
|
|
{
|
|
FastInterruptEnableLock eLock(dLock);
|
|
Thread::yield();
|
|
}
|
|
IRQwakeWaitingThread();
|
|
}
|
|
}
|
|
|
|
template <typename T, unsigned int len>
|
|
void Queue<T,len>::waitUntilNotFull()
|
|
{
|
|
FastInterruptDisableLock dLock;
|
|
IRQwakeWaitingThread();
|
|
while(isFull())
|
|
{
|
|
waiting=Thread::IRQgetCurrentThread();
|
|
Thread::IRQwait();
|
|
{
|
|
FastInterruptEnableLock eLock(dLock);
|
|
Thread::yield();
|
|
}
|
|
IRQwakeWaitingThread();
|
|
}
|
|
}
|
|
|
|
template <typename T, unsigned int len>
|
|
void Queue<T,len>::get(T& elem)
|
|
{
|
|
FastInterruptDisableLock dLock;
|
|
IRQwakeWaitingThread();
|
|
while(isEmpty())
|
|
{
|
|
waiting=Thread::IRQgetCurrentThread();
|
|
Thread::IRQwait();
|
|
{
|
|
FastInterruptEnableLock eLock(dLock);
|
|
Thread::yield();
|
|
}
|
|
IRQwakeWaitingThread();
|
|
}
|
|
numElem--;
|
|
elem=buffer[getPos];
|
|
if(++getPos==len) getPos=0;
|
|
}
|
|
|
|
template <typename T, unsigned int len>
|
|
void Queue<T,len>::put(const T& elem)
|
|
{
|
|
FastInterruptDisableLock dLock;
|
|
IRQwakeWaitingThread();
|
|
while(isFull())
|
|
{
|
|
waiting=Thread::IRQgetCurrentThread();
|
|
Thread::IRQwait();
|
|
{
|
|
FastInterruptEnableLock eLock(dLock);
|
|
Thread::yield();
|
|
}
|
|
IRQwakeWaitingThread();
|
|
}
|
|
numElem++;
|
|
buffer[putPos]=elem;
|
|
if(++putPos==len) putPos=0;
|
|
}
|
|
|
|
template <typename T, unsigned int len>
|
|
bool Queue<T,len>::IRQget(T& elem)
|
|
{
|
|
IRQwakeWaitingThread();
|
|
if(isEmpty()) return false;
|
|
numElem--;
|
|
elem=buffer[getPos];
|
|
if(++getPos==len) getPos=0;
|
|
return true;
|
|
}
|
|
|
|
template <typename T, unsigned int len>
|
|
bool Queue<T,len>::IRQget(T& elem, bool& hppw)
|
|
{
|
|
if(waiting && (waiting->IRQgetPriority() >
|
|
Thread::IRQgetCurrentThread()->IRQgetPriority())) hppw=true;
|
|
IRQwakeWaitingThread();
|
|
if(isEmpty()) return false;
|
|
numElem--;
|
|
elem=buffer[getPos];
|
|
if(++getPos==len) getPos=0;
|
|
return true;
|
|
}
|
|
|
|
template <typename T, unsigned int len>
|
|
bool Queue<T,len>::IRQput(const T& elem)
|
|
{
|
|
IRQwakeWaitingThread();
|
|
if(isFull()) return false;
|
|
numElem++;
|
|
buffer[putPos]=elem;
|
|
if(++putPos==len) putPos=0;
|
|
return true;
|
|
}
|
|
|
|
template <typename T, unsigned int len>
|
|
bool Queue<T,len>::IRQput(const T& elem, bool& hppw)
|
|
{
|
|
if(waiting && (waiting->IRQgetPriority() >
|
|
Thread::IRQgetCurrentThread()->IRQgetPriority())) hppw=true;
|
|
IRQwakeWaitingThread();
|
|
if(isFull()) return false;
|
|
numElem++;
|
|
buffer[putPos]=elem;
|
|
if(++putPos==len) putPos=0;
|
|
return true;
|
|
}
|
|
|
|
//This partial specialization is meant to to produce compiler errors in case an
|
|
//attempt is made to instantiate a Queue with zero size, as it is forbidden
|
|
template<typename T> class Queue<T,0> {};
|
|
|
|
/**
|
|
* An unsynchronized circular buffer data structure with the storage dynamically
|
|
* allocated on the heap.
|
|
* Note that unlike Queue, this class is only a data structure and not a
|
|
* synchronization primitive. The synchronization between the thread and
|
|
* the IRQ (or the other thread) must be done by the caller.
|
|
*/
|
|
template<typename T>
|
|
class DynUnsyncQueue
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor
|
|
* \param elem number of elements of the circular buffer
|
|
*/
|
|
DynUnsyncQueue(unsigned int elem) : data(new T[elem]),
|
|
putPos(0), getPos(0), queueSize(0), queueCapacity(elem) {}
|
|
|
|
/**
|
|
* \return true if the queue is empty
|
|
*/
|
|
bool isEmpty() const { return queueSize==0; }
|
|
|
|
/**
|
|
* \return true if the queue is full
|
|
*/
|
|
bool isFull() const { return queueSize==queueCapacity; }
|
|
|
|
/**
|
|
* \return the number of elements currently in the queue
|
|
*/
|
|
unsigned int size() const { return queueSize; }
|
|
|
|
/**
|
|
* \return the maximum number of elements the queue can hold
|
|
*/
|
|
unsigned int capacity() const { return queueCapacity; }
|
|
|
|
/**
|
|
* Try to put an element in the circular buffer
|
|
* \param elem element to put
|
|
* \return true if the queue was not full
|
|
*/
|
|
bool tryPut(const T& elem);
|
|
|
|
/**
|
|
* Try to get an element from the circular buffer
|
|
* \param elem element to get will be stored here
|
|
* \return true if the queue was not empty
|
|
*/
|
|
bool tryGet(T& elem);
|
|
|
|
/**
|
|
* Erase all elements in the queue
|
|
*/
|
|
void reset() { putPos=getPos=queueSize=0; }
|
|
|
|
/**
|
|
* Destructor
|
|
*/
|
|
~DynUnsyncQueue() { delete[] data; }
|
|
|
|
private:
|
|
DynUnsyncQueue(const DynUnsyncQueue&);
|
|
DynUnsyncQueue& operator=(const DynUnsyncQueue&);
|
|
|
|
T *data;
|
|
unsigned int putPos,getPos;
|
|
volatile unsigned int queueSize;
|
|
const unsigned int queueCapacity;
|
|
};
|
|
|
|
template<typename T>
|
|
bool DynUnsyncQueue<T>::tryPut(const T& elem)
|
|
{
|
|
if(isFull()) return false;
|
|
queueSize++;
|
|
data[putPos++]=elem;
|
|
if(putPos>=queueCapacity) putPos=0;
|
|
return true;
|
|
}
|
|
|
|
template<typename T>
|
|
bool DynUnsyncQueue<T>::tryGet(T& elem)
|
|
{
|
|
if(isEmpty()) return false;
|
|
queueSize--;
|
|
elem=data[getPos++];
|
|
if(getPos>=queueCapacity) getPos=0;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* A class to handle double buffering, but also triple buffering and in general
|
|
* N-buffering. Works between two threads but is especially suited to
|
|
* synchronize between a thread and an interrupt routine.<br>
|
|
* Note that unlike Queue, this class is only a data structure and not a
|
|
* synchronization primitive. The synchronization between the thread and
|
|
* the IRQ (or the other thread) must be done by the caller. <br>
|
|
* The internal implementation treats the buffers as a circular queue of N
|
|
* elements, hence the name.
|
|
* \tparam T type of elements of the buffer, usually char or unsigned char
|
|
* \tparam size maximum size of a buffer
|
|
* \tparam numbuf number of buffers, the default is two resulting in a
|
|
* double buffering scheme. Values 0 and 1 are forbidden
|
|
*/
|
|
template<typename T, unsigned int size, unsigned char numbuf=2>
|
|
class BufferQueue
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor, all buffers are empty
|
|
*/
|
|
BufferQueue() : put(0), get(0), cnt(0) {}
|
|
|
|
/**
|
|
* \return true if no buffer is available for reading
|
|
*/
|
|
bool isEmpty() const { return cnt==0; }
|
|
|
|
/**
|
|
* \return true if no buffer is available for writing
|
|
*/
|
|
bool isFull() const { return cnt==numbuf; }
|
|
|
|
/**
|
|
* \return the maximum size of a buffer
|
|
*/
|
|
unsigned int bufferMaxSize() const { return size; }
|
|
|
|
/**
|
|
* \return the maximum number of buffers
|
|
*/
|
|
unsigned int numberOfBuffers() const { return numbuf; }
|
|
|
|
/**
|
|
* This member function allows to retrieve a buffer ready to be written,
|
|
* if available.
|
|
* \param buffer the available buffer will be assigned here if available
|
|
* \return true if a writable buffer has been found, false otherwise.
|
|
* In this case the buffer parameter is not modified
|
|
*/
|
|
bool tryGetWritableBuffer(T *&buffer)
|
|
{
|
|
if(isFull()) return false;
|
|
buffer=buf[put];
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* After having called tryGetWritableBuffer() to retrieve a buffer and
|
|
* having filled it, this member function allows to mark the buffer as
|
|
* available on the reader side.
|
|
* \param actualSize actual size of buffer. It usually equals bufferMaxSize
|
|
* but can be a lower value in case there is less available data
|
|
*/
|
|
void bufferFilled(unsigned int actualSize)
|
|
{
|
|
if(isFull()) errorHandler(UNEXPECTED);
|
|
cnt++;
|
|
bufSize[put++]=actualSize;
|
|
if(put>=numbuf) put=0;
|
|
}
|
|
|
|
/**
|
|
* \return the number of buffers available for writing (0 to numbuf)
|
|
*/
|
|
unsigned char availableForWriting() const { return numbuf-cnt; }
|
|
|
|
/**
|
|
* This member function allows to retrieve a buffer ready to be read,
|
|
* if available.
|
|
* \param buffer the available buffer will be assigned here if available
|
|
* \param actualSize the actual size of the buffer, as reported by the
|
|
* writer side
|
|
* \return true if a readable buffer has been found, false otherwise.
|
|
* In this case the buffer and actualSize parameters are not modified
|
|
*/
|
|
bool tryGetReadableBuffer(const T *&buffer, unsigned int& actualSize)
|
|
{
|
|
if(isEmpty()) return false;
|
|
buffer=buf[get];
|
|
actualSize=bufSize[get];
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* After having called tryGetReadableBuffer() to retrieve a buffer and
|
|
* having read it, this member function allows to mark the buffer as
|
|
* available on the writer side.
|
|
*/
|
|
void bufferEmptied()
|
|
{
|
|
if(isEmpty()) errorHandler(UNEXPECTED);
|
|
cnt--;
|
|
get++;
|
|
if(get>=numbuf) get=0;
|
|
}
|
|
|
|
/**
|
|
* \return The number of buffers available for reading (0, to numbuf)
|
|
*/
|
|
unsigned char availableForReading() const { return cnt; }
|
|
|
|
/**
|
|
* Reset the buffers. As a consequence, the queue becomes empty.
|
|
*/
|
|
void reset()
|
|
{
|
|
put=get=cnt=0;
|
|
}
|
|
|
|
private:
|
|
//Unwanted methods
|
|
BufferQueue(const BufferQueue&);
|
|
BufferQueue& operator=(const BufferQueue&);
|
|
|
|
T buf[numbuf][size]; // The buffers
|
|
unsigned int bufSize[numbuf]; //To handle partially empty buffers
|
|
unsigned char put; //Put pointer
|
|
unsigned char get; //Get pointer
|
|
volatile unsigned char cnt; //Number of filled buffers, either (0 to numbuf)
|
|
};
|
|
|
|
//These two partial specialization are meant to produce compiler errors in case
|
|
//an attempt is made to allocate a BufferQueue with zero or one buffer, as it
|
|
//is forbidden
|
|
template<typename T, unsigned int size> class BufferQueue<T,size,0> {};
|
|
template<typename T, unsigned int size> class BufferQueue<T,size,1> {};
|
|
|
|
/**
|
|
* \}
|
|
*/
|
|
|
|
} //namespace miosix
|
|
|
|
#endif //QUEUE_H
|