pfcn.c



/*
 * pfcn.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.
 * -------------------------------------------------------------
 *
 *    This module contains the data and methods necessary
 * to create and use prioritized, pre-emptive functions (pfcn's).
 *
 */
#include "types.h"
#include "sizes.h"
#include "err.h"
#include "intrpt.h"
#include "pfcn.h"
/*
 * A prioritized function (pfcn) is a function with no
 * arguments, which returns no value, which is allocated
 * by this module (the pfcn module), which has a
 * priority relative to other pfcns.
 *
 * Once it is posted, a pfcn runs to completion unless
 * it is interrupted by the posting of a pfcn of a
 * higher priority.  In that case the higher priority
 * pfcn runs to completion and then returns control
 * to the one that was running when it was posted.
 *
 * If the posted pfcn does not preempt the pfcn
 * that is currently running, it goes into a prioritized
 * wait list to run when it's priority and then its turn
 * within that priority comes up.
 *
 * The prioritized wait list has the useful property that
 * only one instance of a registered pfcn my reside in
 * the list at a time. Therefore if a pfcn is posted
 * several times before it gets around to running,
 * it runs only once.
 *
 * A pfcn that is running does not reside in the priority
 * wait list, so it is possible for a pfcn to repost
 * itself.
 *
 * Pfcn's use the 'C' stack, the same as hardware
 * interrupts.  That is why they are called pfcn's
 * and not threads. 
 *
 * Pfcn's posted at the same priority run in
 * round-robin fashion.
 *
 * The priority associated with a pfcn may be an
 * integer between 0 (included) and 
 * SYSMAX_PRIORITIES (excluded).  See sizes.h to
 * adjust the number of legal priorities.
 *
 */
/*
 * declarations of private methods of pfcn module 
 */
void insertPfcnEntry(s16 pfdex);
void removePfcnEntry(void);
void callPfcns(s16 pfdex,s16 prevpri);
void dummyPfcn(void);
s16 getFreePfcn(void);

/*
 * Prioritized function (pfcn) table/list entry struct
 */
typedef struct {
    s16 priority;           // priority at which this pfcn is registered
    s16 status;             // unused, inactive, waiting or active
#define PFCN_UNUSED       (0)    // no pfcn registered for this entry
#define PFCN_INACTIVE     (1)    // registered, but not posted
#define PFCN_WAITING      (2)    // posted but not running
#define PFCN_ACTIVE       (3)    // either running or prempted
    void (*pfcn)(void);     // pointer to prioritized function (pfcn)
    s16 prevPfdex;          // index of previous pfcn in list or NIL
    s16 nextPfdex;          // index of next pfcn in list or NIL
} PFCN;

/* -------------------------------------------------------
 * Pfcn module data
 * ------------------------------------------------------- */
    s16 pfcnWaitList=NIL;              // head of pfcn wait list
    s16 currPfdex=NIL;                 // index of currently executing pfcn
    s16 currPriority=NIL;              // priority of currently executing pfcn
    s16 priorityIndex[SYSMAX_PRIORITIES]; // priority index into pfcn wait list
    PFCN pfcnTable[SYSMAX_PFCNS];      // table of pfcn entries and home
                                       // of pfcn wait list

/* ------------------------------------------------------
 *  Public Methods
 * ------------------------------------------------------ */
/*
 * Pfcn_init
 *   Initialize the priority index, the prioritized function
 * table and other pfcn data.
 */
void Pfcn_init(void)
{
    s16 j;

    for(j=0;j<SYSMAX_PFCNS;j++) {          // initialize prioritized function table
         pfcnTable[j].priority=NIL;        //  and list host
         pfcnTable[j].status=PFCN_UNUSED;
         pfcnTable[j].pfcn=dummyPfcn;
         pfcnTable[j].prevPfdex=NIL;
         pfcnTable[j].nextPfdex=NIL;
    }

    for(j=0;j<SYSMAX_PRIORITIES;j++) {      // initialize priority index
         priorityIndex[j]=NIL;              // to the pfcn wait list
    }

    pfcnWaitList=NIL;     // set head of the pfcn wait list
    currPfdex=NIL;        // index of currently executing pfcn
    currPriority=NIL;     // priority of currently executing pfcn
}
/*
 * Pfcn_new
 *    Obtain the index of a pfcn struct, and fill it
 * in with the priority and function pointer supplied.
 *    A bad supplied priority, or failure to find
 * a free PFCN struct in the pfcn array will result in
 * the system error shutdown function being called.
 *   Returns an index into the pfcn table
 * which can be used to post the pfcn.
 */
s16 Pfcn_new(s16 pri,void (*pfcn)(void))
{
    PFCN *pfptr;
    s16 pfdex;
    bool intsave;

    intsave=Intrpt_disable();
    pfdex=getFreePfcn();

    if(pfdex==NIL) {
         Err_shutdown(NO_MORE_PFCNS);
    } else {
         pfptr=&pfcnTable[pfdex];
         pfptr->priority=pri;
         pfptr->status=PFCN_INACTIVE;
         pfptr->pfcn=pfcn;
         pfptr->prevPfdex=NIL;
         pfptr->nextPfdex=NIL;
    }
    Intrpt_restore(intsave);
    return pfdex;
}
/*
 * Pfcn_post
 *
 *      If the pfcn to post has a higher priority than what
 * is running, go ahead and call it, and any other pfcns
 * subsequently found on the wait list with a higher priority
 * than what is currently running.
 *      If it has an equal or lower priority than what is
 * running, just insert it into the appropriate place in the
 * wait list.
 */
void Pfcn_post(s16 pfdex)
{
    s16 prevPf,prevPri;
    bool intsav;
    PFCN *pfptr;

    if(pfdex<0 || pfdex >= SYSMAX_PFCNS) {
        Err_shutdown(BAD_PFCN_POSTED);
    } else {
        pfptr=&pfcnTable[pfdex];
        intsav=Intrpt_disable();
        prevPf=currPfdex;
        prevPri=currPriority;
        if(pfptr->priority>prevPri) {
             callPfcns(pfdex,prevPri);  // call one or more pfcns
        } else if(pfptr->status != PFCN_WAITING){
             insertPfcnEntry(pfdex);  // insert into correct location in pfcn list        
        }
        currPfdex=prevPf;
        currPriority=prevPri;
        Intrpt_restore(intsav);
    }
}

/*
 * Pfcn_priority
 *
 *    Return the priority of the pfcn entry whose
 * pfcnTable index is supplied.  If NIL is returned,
 * that means no entry has been registered with that
 * index.
 */
s16 Pfcn_priority(s16 pfdex)
{
    if(pfdex<0 || pfdex>=SYSMAX_PFCNS) {
        Err_shutdown(BAD_PFCN_INDEX);
    }
    return pfcnTable[pfdex].priority;
}
/*
 * Pfcn_current
 *
 *     Returns the index of the prioritized function (pfcn)
 * which is currently running.  If none, returns NIL
 */
s16 Pfcn_current(void)
{
    return currPfdex;
}
/* ------------------------------------------------------
 *  Private Methods
 * ------------------------------------------------------ */
/*
 * insertPfcnEntry
 *
 *     Insert the priority entry designated by the argument into
 * the pfcn wait list in the appropriate position (sorted from
 * highest to lowest priority). Update the priority index for
 * its priority with the index of the pfdex supplied.
 *
 * Call this routine only with interrupts disabled
 */
void insertPfcnEntry(s16 pfdex)
{
    s16 pfinsert,pridex;
    s16 temp;
    PFCN *pfptr,*tpfptr;
    
    pfptr=&pfcnTable[pfdex];
    /*
     * Set the insert point for this pfcn
     * (it should be the last pfcn having the
     *  supplied priority, or one higher if there is
     *  none with the supplied priority)
     * The entire list should remain sorted from
     * highest to lowest priority.
     */
    pridex=pfptr->priority;
    pfinsert=NIL;
    while(pfinsert==NIL && pridexnextPfdex=NIL;
            pfptr->prevPfdex=NIL;
        } else {
            pfptr->nextPfdex=temp;
            pfptr->prevPfdex=NIL;
            tpfptr->prevPfdex=pfdex;
        }
    } else {      // pfinsert indicates an entry with the same or a higher priority
    /*
     * insert after the pfcn entry whose index is
     * given by pfinsert
     */
        tpfptr=&pfcnTable[pfinsert];
        temp=pfcnTable[pfinsert].nextPfdex;
        if(temp==NIL) {    // this will be last list entry
            tpfptr->nextPfdex=pfdex;
            pfptr->prevPfdex=pfinsert;
            pfptr->nextPfdex=NIL;
        } else {
            pfcnTable[temp].prevPfdex=pfdex;  // link to forward element
            pfptr->nextPfdex=temp;
            tpfptr->nextPfdex=pfdex;  // link to previous element
            pfptr->prevPfdex=pfinsert;
        }
    }
    pfptr->status=PFCN_WAITING;  // mark pfcn entry waiting
    priorityIndex[pfptr->priority]=pfdex;    // set priority index element to pfdex
}
/*
 * removePfcnEntry
 *
 *     Remove the first active pfcn entry
 * from the pfcn waiting list. Mark it
 * active.  Update the priority index
 * to reflect the removal.  Return the
 * function pointer in the pfcn entry.
 *
 *     Call only with interrupts disabled.
 */
void removePfcnEntry(void)
{
     s16 temp;
     PFCN *tptr;
     
 /*
  * remove first pfcn entry from the pfcn wait list
  */
     temp=pfcnWaitList;
     tptr=&pfcnTable[temp];
     pfcnWaitList=tptr->nextPfdex;
     if(pfcnWaitList!=NIL) {
         pfcnTable[pfcnWaitList].prevPfdex=NIL; // new list end has NIL back link
     }
     tptr->nextPfdex=NIL;   // make sure forward link is NIL
                                      // back link should already be NIL
  /*
   * update priority index
   */
     if(priorityIndex[tptr->priority]==temp) {
         priorityIndex[tptr->priority]=NIL;
     }
}
/*
 * callPfcns
 *
 *      Called by Pfcn_post() with the
 * index of the first pfcn to call, and with
 * the priority at which to stop scanning
 * the wait list and calling pfcns.
 *
 * This routine assumes interrupts are
 * disabled, and that the pfcn indicated
 * by pfdex has been allocated, has a
 * higher priority than the second argument,
 * and is not linked into the wait list.
 */
void callPfcns(s16 pfdex,s16 prevpri)
{
    PFCN *pfptr;

    pfptr=&pfcnTable[pfdex];
    do {
        currPfdex=pfdex;
        currPriority=pfptr->priority;
        pfptr->status=PFCN_ACTIVE;
        Intrpt_enable();
        (*pfptr->pfcn)();
        Intrpt_disable();
      /*
       * if it hasn't been re-posted, mark inactive
       * the pfcn entry just completed
       */
        if(pfptr->status!=PFCN_WAITING) {
            pfptr->status=PFCN_INACTIVE;
        }
      /*
       * set pfdex to the head of the wait list,
       * and remove the entry there.
       */
        pfdex=pfcnWaitList;
        if(pfdex!=NIL) {
            pfptr=&pfcnTable[pfdex];
            if(pfptr->priority> prevpri){
                removePfcnEntry();
            }
        } else {
           break;  // exit, wait list empty
        }
    } while (pfptr->priority>prevpri);
}
/*
 * dummyPfcn
 *
 *    This method does an error abort if it is called
 */
void dummyPfcn(void)
{
    Err_shutdown(CALLED_INVALID_PFCN);
}
/*
 * getFreePfcn
 *
 *     Returns the index of the first unused
 * pfcn (prioritized function) entry in the pfcn
 * table.
 *
 */
s16 getFreePfcn(void)
{
    s16 j;
    s16 entry=NIL;

    for(j=0;j<SYSMAX_PFCNS;j++) {
        if(pfcnTable[j].status==PFCN_UNUSED) {
            entry=j;
            break;
        }
    }
    return entry;
}