diff packages/kernel/current/include/mqueue.inl @ 115:6ed91473a1cd ecos-sw-2000-08-21

Merge from eCos master repository on 2000-08-21-22:40:54-BST
author jlarmour
date Fri, 25 Aug 2000 17:32:38 +0000
parents
children f62680ef1804
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/kernel/current/include/mqueue.inl
@@ -0,0 +1,503 @@
+#ifndef CYGONCE_KERNEL_MQUEUE_INL
+#define CYGONCE_KERNEL_MQUEUE_INL
+/*========================================================================
+//
+//      mqueue.inl
+//
+//      Message queues implementation
+//
+//========================================================================
+//####COPYRIGHTBEGIN####
+//                                                                          
+// -------------------------------------------                              
+// The contents of this file are subject to the Red Hat eCos Public License 
+// Version 1.1 (the "License"); you may not use this file except in         
+// compliance with the License.  You may obtain a copy of the License at    
+// http://www.redhat.com/                                                   
+//                                                                          
+// Software distributed under the License is distributed on an "AS IS"      
+// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the 
+// License for the specific language governing rights and limitations under 
+// the License.                                                             
+//                                                                          
+// The Original Code is eCos - Embedded Configurable Operating System,      
+// released September 30, 1998.                                             
+//                                                                          
+// The Initial Developer of the Original Code is Red Hat.                   
+// Portions created by Red Hat are                                          
+// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.                             
+// All Rights Reserved.                                                     
+// -------------------------------------------                              
+//                                                                          
+//####COPYRIGHTEND####
+//========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):     jlarmour
+// Contributors:  
+// Date:          2000-05-09
+// Purpose:       This file provides the implementation for eCos message
+//                queues
+// Description:   This differs from the message boxes also supported
+//                by eCos primarily because the requirements of message
+//                queues are driven by POSIX semantics. POSIX semantics are
+//                more dynamic and therefore heavyweight than Mboxes,
+//                including prioritization, and variable sized queues and
+//                message lengths
+// Usage:         Do not include this file directly - instead
+//                #include <cyg/kernel/mqueue.hxx>
+//
+//####DESCRIPTIONEND####
+//
+//======================================================================
+*/
+
+/* CONFIGURATION */
+
+#include <pkgconf/system.h>
+#include <pkgconf/kernel.h>          // Configuration header
+
+/* INCLUDES */
+
+#include <stddef.h>                  // size_t, NULL
+#include <cyg/infra/cyg_type.h>      // Types
+#include <cyg/kernel/mqueue.hxx>     // Header for this file, just in case
+#include <cyg/infra/cyg_ass.h>       // Assertion support
+#include <cyg/infra/cyg_trac.h>      // Tracing support
+#include <cyg/kernel/sched.hxx>      // scheduler
+#include <cyg/kernel/sched.inl>      // scheduler inlines
+#include <cyg/kernel/sema.hxx>       // Cyg_Counting_Semaphore
+
+#ifdef CYGPKG_ISOINFRA
+# include <string.h>                 // memcpy
+#else
+externC void * memcpy( void *, const void *, size_t );
+#endif
+
+// NOTE:
+// An alternative implementation based on mutexes and condition variables
+// rather than semaphores/scheduler locking was considered. But it was
+// not thought quite as good because it isn't driver safe. You would
+// also have to manage explicitly what counting semaphores do for you
+// intrinsically. Also with the mutex approach, the message queue would
+// be locked the whole time a new entry was being filled in, or copied out
+//
+// It also makes the non-blocking case properly non-blocking rather than
+// still being able to block while waiting for a mutex protecting
+// the message queue internal structures
+
+/* INLINE FUNCTIONS */
+
+//------------------------------------------------------------------------
+
+inline cyg_bool
+Cyg_Mqueue::check_this( cyg_assert_class_zeal zeal ) const
+{
+    if (zeal != cyg_none) {
+        CYG_CHECK_DATA_PTRC(this);  // extreme paranoia
+
+#ifdef CYGDBG_USE_ASSERTS
+        if ( qlen <= 0 || msgsize <= 0 )
+            return false;
+#endif
+
+        if ( queuespacesize < sizeof(struct qentry)+1 )
+            return false;
+
+        CYG_CHECK_DATA_PTRC(queuespace);
+        CYG_CHECK_FUNC_PTRC(free_fn);
+
+        // prevent pre-emption through this. Not so bad since
+        // this is only a diagnostic function
+        Cyg_Scheduler::lock();
+
+        if (NULL != q)
+            CYG_CHECK_DATA_PTRC(q);
+        if (NULL != freelist)
+            CYG_CHECK_DATA_PTRC(freelist);
+        if (NULL != callback)
+            CYG_CHECK_FUNC_PTRC(callback);
+
+        // check each queue entry
+        long msgs=0, busymsgs=0;
+        unsigned int oldprio=0;
+        struct qentry *qtmp;
+
+        if ( NULL != q )
+            oldprio = q->priority;
+        for ( qtmp=q; NULL != qtmp; qtmp=qtmp->next ) {
+            if ( NULL != qtmp->next )
+                CYG_CHECK_DATA_PTRC( qtmp->next );
+
+            // queue should be priority ordered
+            if ( qtmp->priority > oldprio )
+                goto fail;
+            oldprio = qtmp->priority;
+            
+#ifdef CYGDBG_USE_ASSERTS
+            // valid length
+            if ( !qtmp->busy )
+                if ( qtmp->buflen > msgsize )
+                    goto fail;
+#endif
+            if ( qtmp->busy )
+                busymsgs++;
+            else
+                msgs++;
+        } // for
+        
+        long freemsgs=0;
+        
+        // check that number of used and unused messages == q length
+        for ( qtmp=freelist; NULL != qtmp; qtmp=qtmp->next ) {
+            if ( NULL != qtmp->next )
+                CYG_CHECK_DATA_PTRC( qtmp->next );
+            if ( qtmp->busy )
+                busymsgs++;
+            else
+                freemsgs++;
+        }
+
+#ifdef CYGDBG_USE_ASSERTS
+        // and sum of all messages should be the total q length
+        if ( qlen != (msgs+freemsgs+busymsgs) )
+            goto fail;
+#endif
+        
+        Cyg_Scheduler::unlock();
+
+    }
+    return true; // object OK
+ fail:
+    Cyg_Scheduler::unlock();
+    return false; // object fubar'd
+}
+
+//------------------------------------------------------------------------
+
+inline
+Cyg_Mqueue::Cyg_Mqueue( long maxmsgs, long maxmsgsize,
+                        qalloc_fn_t qalloc, qfree_fn_t qfree, qerr_t *err )
+    : putsem(maxmsgs), getsem(0)
+{
+    CYG_REPORT_FUNCTION();
+    CYG_REPORT_FUNCARG5( "maxmsgs=%ld, maxmsgsize=%ld, qalloc=%08x, "
+                         "qfree=%08x, &err=%08x", maxmsgs, maxmsgsize,
+                         qalloc, qfree, err);
+    CYG_PRECONDITIONC( (maxmsgs > 0) && (maxmsgsize > 0) );
+    CYG_CHECK_DATA_PTRC( err );
+    CYG_CHECK_FUNC_PTRC( qalloc );
+    CYG_CHECK_FUNC_PTRC( qfree );
+
+    // mem to allocate for entire queue size. Also wants to be rounded
+    // up so that the structs are aligned.
+    const long addralign = sizeof(void *) - 1;
+    long entrysize = (sizeof(struct qentry) + maxmsgsize + addralign)
+       & ~addralign;
+
+    queuespacesize = entrysize * maxmsgs;
+    queuespace = qalloc( queuespacesize );
+
+    if (NULL == queuespace) {
+        *err=NOMEM;
+        CYG_REPORT_RETURN();
+        return;
+    }
+
+    // link up freelist
+    long i;
+    struct qentry *qtmp;
+    for ( i=0, qtmp=(struct qentry *)queuespace;
+          i<maxmsgs-1;
+          i++, qtmp=qtmp->next ) {
+        qtmp->busy = false;
+        qtmp->next = (struct qentry *)((char *)qtmp + entrysize);
+    } // for
+
+    freelist   = (struct qentry *)queuespace;
+
+    // set the last entry in the chain to the start to make the list circular
+    qtmp->next = NULL;
+    callback   = NULL;
+    q          = NULL;
+    free_fn    = qfree;
+#ifdef CYGDBG_USE_ASSERTS
+    qlen       = maxmsgs;
+    msgsize    = maxmsgsize;
+#endif
+
+    *err = OK;
+
+    // object should be valid now
+    CYG_ASSERT_THISC();
+
+    CYG_REPORT_RETURN();
+}
+
+//------------------------------------------------------------------------
+
+inline
+Cyg_Mqueue::~Cyg_Mqueue()
+{
+    CYG_REPORT_FUNCTION();
+
+    if ( NULL != queuespace ) {
+        // object should be valid if queuespace was successfully allocated
+        CYG_ASSERT_THISC();
+        free_fn( queuespace, queuespacesize );
+    }
+
+#ifdef CYGDBG_USE_ASSERTS
+    qlen = msgsize = 0; // deliberately make it fail check_this() if used
+#endif
+
+    CYG_REPORT_RETURN();
+}
+
+//------------------------------------------------------------------------
+
+// put() copies len bytes of *buf into the queue at priority prio
+inline Cyg_Mqueue::qerr_t
+Cyg_Mqueue::put( const char *buf, size_t len, unsigned int prio, bool block )
+{
+    CYG_REPORT_FUNCTYPE( "err=%d");
+    CYG_REPORT_FUNCARG4( "buf=%08x, len=%ld, prio=%ud, block=%d",
+                         buf, len, prio, block==true );
+    CYG_CHECK_DATA_PTRC( buf );
+    CYG_ASSERT_THISC();
+    CYG_PRECONDITIONC( len <= (size_t)msgsize );
+
+    qerr_t err;
+    struct qentry *qtmp, *qent;
+
+    // wait till a freelist entry is available
+    if ( true == block ) {
+        if ( false == putsem.wait() ) {
+            err = INTR;
+            goto exit;
+        }
+    } else { 
+        if ( false == putsem.trywait() ) {
+            err = WOULDBLOCK;
+            goto exit;
+        }
+    }
+
+    // prevent preemption when fiddling with important members
+    Cyg_Scheduler::lock();
+
+    CYG_ASSERT_THISC();
+
+    // get a queue entry from the freelist
+    // don't need to check the freelist - the semaphore tells us there's
+    // definitely a usable non-busy one there. It's just a question of
+    // locating it.
+
+    if (!freelist->busy) { // fast-track common case
+        qent     = freelist;
+        freelist = freelist->next;
+    } else {
+        for ( qtmp=freelist; !qtmp->next->busy; qtmp=qtmp->next )
+            CYG_EMPTY_STATEMENT; // skip through
+        qent       = qtmp->next;
+        qtmp->next = qent->next;
+    }
+            
+    // now put it in place in q
+
+    if ( NULL == q ) {
+        q = qent;
+        q->next = NULL;
+    } else {
+        struct qentry **qentp;
+
+        // insert into queue according to prio
+        for ( qentp=&q; NULL != *qentp; qentp = &((*qentp)->next) ) {
+            if ((*qentp)->priority < prio)
+                break;
+        } // for
+
+        qent->next = *qentp;
+        *qentp = qent;
+    } // else
+    
+    qent->priority = prio; // have to set this now so when the sched is
+                           // unlocked, other qent's can be added in the
+                           // right place
+    qent->busy = true; // let things know this entry should be ignored until
+                       // it's finished having its data copied
+
+    // unlock the scheduler, and potentially switch threads, but
+    // that's okay now. We don't want it locked for the expensive memcpy
+    Cyg_Scheduler::unlock();
+
+    qent->buflen   = len;
+    memcpy( qent->buf(), buf, len );
+
+    // make available now - setting non-atomically is alright if you think
+    // about it - the only thing that matters is that it's completed before
+    // the post()
+    qent->busy = false;
+
+    // if we have to notify someone, we only do it if no-one's already
+    // sitting waiting for a message to appear, AND if it's a transition
+    // from empty to non-empty
+
+    if ( callback != NULL && !getsem.waiting() && (0 == getsem.peek()) ) {
+        getsem.post();        
+        callback( *this, callback_data );
+    } else
+        getsem.post();        
+
+    err = OK;
+
+ exit:
+    CYG_ASSERT_THISC();
+    CYG_REPORT_RETVAL(err);
+    return err;
+} // Cyg_Mqueue::put()
+
+//------------------------------------------------------------------------
+
+
+// get() returns the oldest highest priority message in the queue in *buf
+// and sets *prio to the priority (if prio is non-NULL) and *len to the
+// actual message size
+
+inline Cyg_Mqueue::qerr_t
+Cyg_Mqueue::get( char *buf, size_t *len, unsigned int *prio, bool block )
+{
+    CYG_REPORT_FUNCTYPE( "err=%d");
+    CYG_REPORT_FUNCARG4( "buf=%08x, len=%08x, prio=%08x, block=%d",
+                         buf, len, prio, block==true );
+    CYG_CHECK_DATA_PTRC( buf );
+    CYG_CHECK_DATA_PTRC( len );
+    if ( NULL != prio )
+        CYG_CHECK_DATA_PTRC( prio );
+    CYG_ASSERT_THISC();
+
+    qerr_t err;
+    struct qentry *qent;
+
+    // wait till a q entry is available
+    if ( true == block ) {
+        if ( false == getsem.wait() ) {
+            err = INTR;
+            goto exit;
+        }
+    } else { 
+        if ( false == getsem.trywait() ) {
+            err = WOULDBLOCK;
+            goto exit;
+        }
+    }
+
+    // prevent preemption when fiddling with important members
+    
+    Cyg_Scheduler::lock();
+    
+    // don't need to check the q - the semaphore tells us there's
+    // definitely a usable non-busy one there. It's just a question of
+    // locating it.
+    
+    if ( !q->busy ) {   // fast-track the common case
+        qent       = q;
+        q          = qent->next;
+    } else {
+        struct qentry *qtmp;
+
+        for ( qtmp=q; !qtmp->next->busy; qtmp=qtmp->next )
+            CYG_EMPTY_STATEMENT; // skip through
+
+        qent = qtmp->next;
+        qtmp->next = qent->next;
+    } // else
+
+    // now stick at front of freelist, but marked busy
+    qent->next = freelist;
+    freelist   = qent;
+
+    qent->busy = true; // don't let it truly be part of the freelist just yet
+                       // till the data is copied out
+
+    // unlock the scheduler, and potentially switch threads, but
+    // that's okay now. We don't want it locked for the expensive memcpy
+    Cyg_Scheduler::unlock();
+
+    *len  = qent->buflen;
+    if ( NULL != prio )
+        *prio = qent->priority;
+    memcpy( buf, qent->buf(), *len );
+
+    // make available now - setting non-atomically is alright if you think
+    // about it - the only thing that matters is that it's completed before
+    // the post()
+    qent->busy = false;
+
+    putsem.post();
+
+    err = OK;
+
+ exit:
+    CYG_ASSERT_THISC();
+    CYG_REPORT_RETVAL(err);
+    return err;
+    
+} // Cyg_Mqueue::get()
+
+//------------------------------------------------------------------------
+
+// count() returns the number of messages in the queue
+inline long
+Cyg_Mqueue::count()
+{
+    CYG_REPORT_FUNCTYPE("curmsgs=%d");
+    
+    long curmsgs = (long)getsem.peek();
+
+    CYG_REPORT_RETVAL(curmsgs);
+    return curmsgs;    
+} // Cyg_Mqueue::count()
+
+//------------------------------------------------------------------------
+
+
+// Supply a callback function to call (with the supplied data argument)
+// when the queue goes from empty to non-empty (unless someone's already
+// doing a get()). This returns the old callback_fn, and if olddata is
+// non-NULL sets it to the old data (yes, really!)
+inline Cyg_Mqueue::callback_fn_t
+Cyg_Mqueue::setnotify( callback_fn_t callback_fn, CYG_ADDRWORD data,
+                       CYG_ADDRWORD *olddata)
+{
+    CYG_REPORT_FUNCTYPE("old callback=%08x");
+    CYG_REPORT_FUNCARG3XV( callback_fn, data, olddata );
+    if ( NULL != callback_fn )
+        CYG_CHECK_FUNC_PTRC( callback_fn );
+    if (NULL != olddata)
+        CYG_CHECK_DATA_PTRC( olddata );
+
+    callback_fn_t oldfn;
+
+    // Need to prevent preemption for accessing common structures
+    // Just locking the scheduler has the least overhead
+    Cyg_Scheduler::lock();
+
+    oldfn = callback;
+    if (NULL != olddata)
+        *olddata = callback_data;
+
+    callback_data = data;
+    callback      = callback_fn;
+
+    Cyg_Scheduler::unlock();
+
+    CYG_REPORT_RETVAL(oldfn);
+    return oldfn;
+}
+
+//------------------------------------------------------------------------
+
+#endif /* CYGONCE_KERNEL_MQUEUE_INL multiple inclusion protection */
+
+/* EOF mqueue.inl */