/*************************************************************************** * 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 * ***************************************************************************/ #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.
* 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.
* 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 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.
* 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.
* 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.
* 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.
* 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.
* 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 void Queue::waitUntilNotEmpty() { FastInterruptDisableLock dLock; IRQwakeWaitingThread(); while(isEmpty()) { waiting=Thread::IRQgetCurrentThread(); Thread::IRQwait(); { FastInterruptEnableLock eLock(dLock); Thread::yield(); } IRQwakeWaitingThread(); } } template void Queue::waitUntilNotFull() { FastInterruptDisableLock dLock; IRQwakeWaitingThread(); while(isFull()) { waiting=Thread::IRQgetCurrentThread(); Thread::IRQwait(); { FastInterruptEnableLock eLock(dLock); Thread::yield(); } IRQwakeWaitingThread(); } } template void Queue::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 void Queue::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 bool Queue::IRQget(T& elem) { IRQwakeWaitingThread(); if(isEmpty()) return false; numElem--; elem=buffer[getPos]; if(++getPos==len) getPos=0; return true; } template bool Queue::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 bool Queue::IRQput(const T& elem) { IRQwakeWaitingThread(); if(isFull()) return false; numElem++; buffer[putPos]=elem; if(++putPos==len) putPos=0; return true; } template bool Queue::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 class Queue {}; /** * 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 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 bool DynUnsyncQueue::tryPut(const T& elem) { if(isFull()) return false; queueSize++; data[putPos++]=elem; if(putPos>=queueCapacity) putPos=0; return true; } template bool DynUnsyncQueue::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.
* 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.
* 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 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 class BufferQueue {}; template class BufferQueue {}; /** * \} */ } //namespace miosix #endif //QUEUE_H