pipe.c



/*
 * pipe.c
 *
 * ------------------------------------------------------------
 * A Kla2 Module
 * Copyright (c) 2003, David Clifton
 * All Rights Reserved
 * http://www.codelode.com
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice, website reference, this permission
 * notice, and the disclaimer of warranty below shall be included
 * in all copies, derivatives, or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * -------------------------------------------------------------
 *
 *    data & methods for pipe object
 *
 * NOTES:
 *
 * 1) It is a useful practice to put each individual pipe object
 * into its own source file, which contains a pointer to its allocated
 * PIPE structure, the contiguous buffer area, and wrapper functions
 * (which eliminate the need to pass the PIPE * pointer) for each
 * of the Pipe methods used by the application.
 *
 * 2) It is up to the user to determine what action to take if
 * the pipe fills up.  This action may be to simply discard
 * the input data, or to abort the program, or to wait for
 * the pipe to be read (only works if the receiver is at a
 * higher priority which is usually not the case).  Pipe overflow
 * can be a useful tool to determine whether the system
 * has enough processing power to meet its obligations.
 *
 * 3) Buffers are retrieved from the pipe in the order in
 * which they were put, not necessarily the order in which
 * they were allocated.
 */
#include "types.h"
#include "sizes.h"
#include "err.h"
#include "intrpt.h"
#include "pfcn.h"
#include "pipe.h"
/*
 * buffer node struct
 */    
struct bufnode {
    u8 *bufptr;
    s16 bnnext;
};
/*
 * BUFNODE type
 */
typedef struct bufnode BUFNODE;
/*
 * private method declarations
 */
PIPE *firstFreePipe(void);
s16 firstFreeBnode(void);
void *moveFreeToAllocd(PIPE *pipeptr);
void moveAllocdToFilled(PIPE *pipeptr,void *bufptr);
void *moveFirstFilledToRecvd(PIPE *pipeptr);
void moveRecvdToFree(PIPE *pipeptr,void *bufptr);

/* ---------------------------------------------------------------
 * Pipe object data
 * --------------------------------------------------------------- */
/*
 * A pointer to the dummy buffer is returned if a user
 * calls Pipe_bufalloc() with a NULL (PIPE *) argument.
 * This fact can be used to permit the pipe supplier
 * to put data to a pipe before the pipe has been
 * allocated by its receiver.  Such data will not
 * take up a pipe buffer, or be transmitted to the
 * receiver.
 */
u8 dummyBuffer[SYSMAX_PIPEBUF_SIZE];
/*
 * initialized pipe code constant
 */
const s16 initPipeCode= 0xBEAD;
/*
 * table of pipe structs
 */
PIPE pipeTable[SYSMAX_PIPES];
/*
 * table of pipe buffer nodes (contains pointers to buffers)
 */
BUFNODE pbufTable[SYSMAX_PIPEBUFS];
/*
 * first Free buffer node
 */
s16 firstFreeBufnode=NIL;
/*----------------------------------------------------------------
 * Public method definitions
 * --------------------------------------------------------------- */
/*
 * Pipe_init
 *    Initialize the pipe table and the pipe buffer table
 */
void Pipe_init(void)
{
    s16 j;

/*
 * initialize all the pipe table entries
 */
    for(j=0;j<SYSMAX_PIPES;j++) {
        pipeTable[j].ipcode=initPipeCode;  // flag memory as a pipe struct
        pipeTable[j].bnum=0;
        pipeTable[j].bsiz=0;
        pipeTable[j].freeList=NIL;
        pipeTable[j].allocdList=NIL;
        pipeTable[j].filledListFirst=NIL;
        pipeTable[j].filledListLast=NIL;
        pipeTable[j].recvdList=NIL;
        pipeTable[j].recvPfcn=NIL;
    }
/*
 * initialize all the pipe buffer nodes
 * and put them into the pipe buffer node
 * free chain
 */
    firstFreeBufnode=0;
    for(j=0;j<SYSMAX_PIPEBUFS;j++) {
        pbufTable[j].bufptr=NULL;
        if((j+1)==SYSMAX_PIPEBUFS) {
            pbufTable[j].bnnext=NIL;
        } else {
            pbufTable[j].bnnext=j+1;
        }
    }
}
/*
 * Pipe_new
 *
 * Allocate a new PIPE struct.
 *
 * Initialize the struct with the number and size of pipe
 * buffers, the location of a contiguous block of memory
 * large enough to contain all the pipe buffers, and the
 * pfcn index of a registered prioritized function
 * to use as a buffer receive function.
 *
 * If the receive function index is NIL, nobody will be notified
 * when a buffer is placed into the pipe.  Such a pipe must
 * be polled by using the Pipe_dataAvailable() method.
 *
 * Returns a pointer to the PIPE struct allocated.
 *
 * Does an error shutdown if there are no PIPE structs
 * left to allocate.
 *
 * NOTE:
 *   If interrupt latency is an important issue, do all
 * of the pipe allocations before the main processes are
 * started.
 *   Notice that there is no method for de-allocating
 * a pipe.  Dynamic Pipe allocation after initialization
 * is discouraged.
 */
PIPE *Pipe_new(s16 bufno,s16 bufsiz,void *bufptr,s16 rcvpfcn)
{
    PIPE *dapipe;
    u8 *baddr;        // address of next buffer
    s16 bndex,bprev,numbufs; // pointers to buffer nodes
    bool intsav;

    intsav=Intrpt_disable();
    dapipe=firstFreePipe();
    if(dapipe==NULL) {
        Err_shutdown(NO_MORE_PIPES);
    } else {
        if(bufno<1) {
            Err_shutdown(NEED_ATLEAST_ONE_PIPEBUF);
        } else {
            dapipe->bnum=bufno;
        }
        if(bufsiz>SYSMAX_PIPEBUF_SIZE) {
            Err_shutdown(BAD_PIPE_BUFSIZE);
        } else {
            dapipe->bsiz=bufsiz;
        }
        if(bufptr==NULL) {
            Err_shutdown(NO_PIPE_BUFFER_SPACE);
        } else {
            dapipe->bptr=(u8 *)bufptr;
        }
        dapipe->recvPfcn=rcvpfcn;
      /*
       * build the pipe's free chain of buffer nodes
       * with pointers to associated buffers in
       * the space provided
       */
        bprev=NIL;
        bndex=firstFreeBnode();

        if(bndex==NIL) {
            Err_shutdown(NO_MORE_PIPE_BUFNODES);
        } else {
            baddr=dapipe->bptr;
            numbufs=0;
            do {
                if(bprev==NIL) {
                    dapipe->freeList=bndex;
                } else {
                    pbufTable[bprev].bnnext=bndex;
                }
                pbufTable[bndex].bufptr=baddr;
                bprev=bndex;
                bndex=firstFreeBnode();
                if(bndex==NIL) {
                    Err_shutdown(NO_MORE_PIPE_BUFNODES);
                } else {
                    numbufs++;
                    baddr=baddr+dapipe->bsiz;
                }
            } while ( numbufsbnum );
            pbufTable[bprev].bnnext=NIL;
        }
    }
    Intrpt_restore(intsav);
    return dapipe;
}
/*
 * Pipe_bufalloc
 *
 *      This routine checks to see if there is a buffer 
 * available for use in the contiguous buffer area.  If
 * so, it returns a pointer to the next available such
 * buffer.  If not it returns NULL.
 *
 * If the PIPE pointer supplied is NULL, this routine 
 * returns a pointer to the dummy buffer.  The user
 * may fill that buffer and re-present it using the
 * Pipe_putbuf() method, but it will not be placed
 * into any pipe.
 *
 * This feature allows the user to put data to a
 * pipe before it is initialized by the receiver,
 * though all data put to a pipe before it is
 * initialized is lost.
 *
 * If the user passes a PIPE pointer other than
 * NULL, which does not point to an initialize pipe
 * an error shutdown occurs.
 */
void *Pipe_bufalloc(PIPE *pipeptr)
{
    bool intsav;
    void *ptr=NULL;

    intsav=Intrpt_disable();
    if(pipeptr==NULL) {
        ptr=(void *)dummyBuffer;
    } else if (pipeptr->ipcode != initPipeCode) {
        Err_shutdown(BAD_PIPE_POINTER);
    } else if(pipeptr->freeList == NIL) {
        ptr=NULL;
    } else {
        ptr=moveFreeToAllocd(pipeptr);
    }
    Intrpt_restore(intsav);
    return ptr;
}
/*
 * Pipe_putbuf
 *
 *     This routine adds the bnode for the buffer supplied
 * to the end of the filled buffer chain.
 */
void Pipe_putbuf(PIPE *pipeptr,void *bufptr)
{
    bool intsav;

    if(pipeptr->ipcode != initPipeCode) {
        Err_shutdown(PIPE_PTR_ERROR);
    } else if(bufptr != NULL) {
        intsav=Intrpt_disable();
        moveAllocdToFilled(pipeptr,bufptr);  // move bufptr associated bnode to filled list
        Intrpt_restore(intsav);
        if(pipeptr->recvPfcn!=NIL) {         // if there is a receive function
            Pfcn_post(pipeptr->recvPfcn);  // post it
        }
    }
}
/*
 * Pipe_getbuf
 *
 *     This routine returns a pointer to the next filled buffer in
 * the pipe indicated by the argument.  If there is no next filled
 * buffer, it returns NULL.  
 */
void *Pipe_getbuf(PIPE *pipeptr)
{
    bool intsav;
    void *ptr;

    intsav=Intrpt_disable();
    ptr=moveFirstFilledToRecvd(pipeptr);
    Intrpt_restore(intsav);
    return ptr;
}
/*
 * Pipe_freebuf
 *
 *     Free the buffer whose pointer is supplied in
 * the second argument to the method.  If there is 
 * another buffer waiting to be received, go ahead
 * and  post its receive pfcn (prioritized function), since
 * it's possible that the invocation for that buffer was ignored due
 * to the fact that the thread was already running.
 * Invoking the thread again will do no harm since multiple
 * invocations prior to activation of the pfcn have
 * no more effect than a single invocation.
 */
void Pipe_freebuf(PIPE *pipeptr,void *bufptr)
{
    bool intsav;

    intsav=Intrpt_disable();
    moveRecvdToFree(pipeptr,bufptr);
    if(Pipe_dataAvailable(pipeptr)){
        Intrpt_restore(intsav);
        if(pipeptr->recvPfcn!=NIL) {
            Pfcn_post(pipeptr->recvPfcn);
        }
    } else {
        Intrpt_restore(intsav);
    }
}
/*
 * Pipe_dataAvailable
 * 
 *    If there are any buffers ready to be read
 * return TRUE, otherwise, return FALSE.
 */
bool Pipe_dataAvailable(PIPE *pipeptr)
{
    bool ret,intsav;

    intsav=Intrpt_disable();
    if( pipeptr->filledListFirst != NIL)  {
        ret= TRUE;
    } else {
        ret= FALSE;
    }
    Intrpt_restore(intsav);
    return ret;
}
/* ---------------------------------------------------------
 * private pipe methods
 * --------------------------------------------------------- */
/*
 * firstFreePipe
 *
 *    returns a pointer to the first free pipe in the
 * pipeTable[].  If there is no free pipe in the table,
 * returns NULL.
 *
 * Call this method only with interrupts disabled.
 *
 */
PIPE *firstFreePipe(void)
{
    PIPE *ret=NULL;
    s16 j;

    for(j=0;j<SYSMAX_PIPES;j++) {
        if(pipeTable[j].bnum==0) {
            ret= &pipeTable[j];     // point to the PIPE struct
            pipeTable[j].bnum=NIL;  // just so it won't be zero until the
                                    // pipe is initialized
            break;
        }
    }
    return ret;
}
/*
 * firstFreeBnode
 *
 *    returns the pbufTable[] index of the first free
 * buffer entry (bnode) in the table.  returns NIL if there
 * are no free entries.
 *
 * Call this method only when interrupts are disabled.
 *
 */
s16 firstFreeBnode(void)
{
    s16 retval;

    retval=firstFreeBufnode;
    if(retval!=NIL) {
        firstFreeBufnode=pbufTable[retval].bnnext;
    }
    return retval;
}
/* 
 * moveFreeToAllocd
 *
 *     For the PIPE pointed to by the first argument, move the
 * buffer node indexed by the second argument from the free list
 * to the alloc'd list.  Move the first buffer node on the free
 * list to the first position on the alloc'd list.  Return a 
 * pointer to the associated buffer.
 *
 * Call this method only when interrupts are disabled
 */
void *moveFreeToAllocd(PIPE *pptr)
{
     s16 bnode,bnext;

     bnode=pptr->freeList;                    // remove first bnode
     pptr->freeList=pbufTable[bnode].bnnext;  // from freeList
     bnext=pptr->allocdList;                  // put it into
     pptr->allocdList=bnode;                  // first position
     pbufTable[bnode].bnnext=bnext;           // in allocd list
     return (void *)pbufTable[bnode].bufptr;  // return buffer ptr
}
/*
 * moveAllocdToFilled
 *
 *     Locate in the allocdList of the PIPE to which the
 * first argument points, the bnode associated with the
 * buffer pointer in the second argument.  If no such
 * bnode, call Err_shutdown().
 *
 * Remove the bnode found from the allocdList, and
 * place it at the end of the filled list.
 *
 * Call this routine only with interrupts disabled
 */
void moveAllocdToFilled(PIPE *pptr,void *bufptr)
{
    s16 bprev,bnext,bnode;
    BUFNODE *bnptr;

    bprev=NIL;
    bnode=NIL;
    bnext=pptr->allocdList;
   /*
    * find allocd bnode with bufptr = second argument
    */
    while(bnext!=NIL) {
        bnptr=&pbufTable[bnext];
        if(bnptr->bufptr==(u8 *)bufptr) {
            if(bprev==NIL) {
                pptr->allocdList=bnptr->bnnext;
            } else {
                pbufTable[bprev].bnnext=bnptr->bnnext;
            }
            bnode=bnext;
            break;
        } else {
            bprev=bnext;
            bnext=bnptr->bnnext;
        }
    }
    if(bnode==NIL) {
        Err_shutdown(BNODE_NOT_IN_ALLOCD_LIST);
    } else {
      /*
       * put bnode found onto end of filled list
       */
        if(pptr->filledListFirst==NIL) {    // filled list is empty
            pptr->filledListFirst=bnode;    // make bnode
            pptr->filledListLast=bnode;     // only entry
        } else {                            // otherwise
            bprev=pptr->filledListLast;     // add bnode to
            pbufTable[bprev].bnnext= bnode; // end of filled list
        }
        pbufTable[bnode].bnnext=NIL;
    }
}
/*
 * moveFirstFilledToRecvd
 *
 *     If the indicated pipe's filled list is empty,
 * just return NULL.  Otherwise, move the first bnode
 * in the filled list to the head of the received list
 * and return the associated buffer pointer.
 *
 * Call this routine only with interrupts disabled.
 */
void *moveFirstFilledToRecvd(PIPE *pipeptr)
{
    void *bufptr=NULL;
    s16 bnode;

    bnode=pipeptr->filledListFirst;
    if(bnode!=NIL) {
        pipeptr->filledListFirst=pbufTable[bnode].bnnext;
        pbufTable[bnode].bnnext=pipeptr->recvdList;
        pipeptr->recvdList=bnode;
        bufptr=pbufTable[bnode].bufptr;
    }
    return bufptr;
}
/*
 * moveRecvdToFree
 *
 *    If the bufptr supplied is not in a bnode
 * in the recvd list, then call Err_shutdown.
 * if it is in the recvd list, remove it from
 * that list and add it to the head of the
 * pipe's free list.
 *
 * Call this routine only with interrupts disabled 
 */
void moveRecvdToFree(PIPE *pptr,void *bufptr)
{
    s16 bprev,bnext,bnode;

    bprev=NIL;
    bnode=NIL;
    bnext=pptr->recvdList;
   /*
    * find recvd bnode with bufptr = second argument
    */
    while(bnext!=NIL) {
        if(pbufTable[bnext].bufptr==(u8 *)bufptr) {
            if(bprev==NIL) {
                pptr->recvdList=pbufTable[bnext].bnnext;
            } else {
                pbufTable[bprev].bnnext=pbufTable[bnext].bnnext;
            }
            bnode=bnext;
            break;
        } else {
            bprev=bnext;
            bnext=pbufTable[bprev].bnnext;
        }
    }
    if(bnode==NIL) {
        Err_shutdown(BNODE_NOT_IN_RECVD_LIST);
    } else {
      /*
       * put bnode found onto front of freeList
       */
        pbufTable[bnode].bnnext=pptr->freeList;
        pptr->freeList=bnode;
    }
}