
/*
 *  QINT.C
 *
 *  Prioritized Interrupt scheme based on exceptions (and therefore
 *  standard signals).
 *
 *  NOTE the following bugs in EXEC exception handling:
 *	-If you ever use Forbid()/Permit() pair, An exception which comes
 *	 in within the pair will not get processed after the Permit()
 *	 until one of several system calls is made to force a re-evaluation.
 *
 *	-We cannot simply return a mask at the end of the exception
 *	 handler because if a signal comes in after the exception occurs
 *	 but before we return, the same problem as with Forbid()/Permit()
 *	 comes up.
 *
 *  Calling SetExcept() while NOT Forbid()n appears to fix the problem.
 */

#include <exec/types.h> 	/*  Includes.  You might have to add some   */
#include <exec/nodes.h> 	/*  If I missed any.			    */
#include <exec/lists.h>
#include <exec/tasks.h>

typedef unsigned char	ubyte;
typedef unsigned short	uword;
typedef unsigned long	ulong;
typedef struct Task	TASK;
typedef struct Node	NODE;
typedef struct List	LIST;
typedef void		(*FPTR)();

extern TASK *FindTask();

#define QINT	struct _QINT

QINT {			    /*	32 bytes total		*/
    NODE    Node;	    /*	14 bytes		*/
    FPTR    vector;	    /*	Function to call	*/
    long    sigmask;	    /*	signal mask (1 bit set) */
    long    arg;	    /*	argument to handler	*/
    ubyte   filler2[6];
};

/*
 *  Note: Since -128 is the lowest priority possible, setting an
 *  exception's priority to -128 means it will never occur.
 */

static char QPri = -128;   /*  Current process Q interrupt priority	   */
static QINT QInt[32];	   /*  Q interrupts which are really exceptions    */
static LIST QList;	   /*  List of pending Q interrupts		   */
static APTR QSaveExcept;
static short QInts;
static short QInHan;	   /*  Currently in the exception queue handler    */

void except();
void QInit();

#asm
	    ;	_EXCEPT
	    ;
	    ;	Exception handler.
	    ;
	    ;	ALL data and address registers are saved by EXEC.  D0
	    ;	holds the exceptions that occured on entry, and A1
	    ;	holds the exception data frame (which we do not use,
	    ;	but need to save in case there are exceptions that
	    ;	we do not own).

	    public  _LVOForbid
	    public  _LVOPermit
	    public  _LVOEnqueue
	    public  _LVOSetSignal
	    public  _geta4		;Aztec: Load proper address reg.
	    public  _QSaveExcept
	    public  _QInHan

_except:
	    move.l	A1,-(sp)        ;save except data segment
	    move.l	4,A6		;A6 = Exec Base
	    move.l	D0,D6		;D6 = Exception Bit Mask
	    jsr 	_geta4		;get global base register (Aztec small code)
	    ;;sub.w	  #1,_QLevel	  ;level down!
	    ;;bpl	  .ex0
	    ;;				  ;   STACKING LIMIT REACHED
	    ;;add.w	  #1,_QLevel	  ;down too far!
	    ;;add.l	  #1,_QError	  ;mark it
	    ;;jsr	  _LVOForbid(A6)  ;Reload all exceptions that occured
	    ;;move.l	  D6,D0
	    ;;move.l	  D6,D1
	    ;;jsr	  _LVOSetSignal(A6)
	    ;;jsr	  _LVOPermit(A6)
	    ;;move.l	  D6,D0 	  ;Reenable all exceptions that
	    ;;				  ; occured by returning the mask in D0
	    ;;addq.l	  #4,sp 	  ;restore stack frame
	    ;;rts

	    ;	Queue any exceptions which are interrupts.  Exceptions
	    ;	for interrupts which are queued are NOT reenabled until
	    ;	they are actually run.
	    ;
	    ;	Note that in the loop we must loop to .ex2 to decrement
	    ;	D4, which doesn't occur when we find a '1'.  The Z bit
	    ;	must be set when we loop to .ex2

.ex0	    moveq.l	#31,D4		;D4 = BIT NUMBER
.ex1	    btst.l	D4,D6		; test bits
.ex2	    dbne	D4,.ex1 	; until found a '1'
	    beq 	.ex10		;or loop exhausted (D4 == -1)

	    move.l	D4,D5		;Calculate address of QINT.
	    asl.l	#5,D5		;D5 = index * sizeof(QINT)
	    add.l	#_QInt,D5	;     + Address
	    move.l	D5,A3		;A3 = QINT address
	    tst.l	14(A3)          ;Is this exception vectored?
	    beq 	.ex2		;no, somebody else owns it

.ex3	    bclr.l	D4,D6		;clear exception bit.
	    jsr 	_LVOForbid(A6)  ;Important operation!
	    move.l	A3,A1		;A1 = node
	    lea.l	_QList,A0	;A0 = List base
	    move.b	#5,8(A1)        ;mark as being queued
	    jsr 	_LVOEnqueue(A6)
	    jsr 	_LVOPermit(A6)  ;enable exceptions
	    clr.w	D7		;Force Z cc set.
	    bra 	.ex2

	    ;	Call the handler.  NOTE that handler() need only
	    ;	save/restore D6.  Both Lattice and Aztec will do this.

.ex10	    tst.w	_QInHan 	;no need to call handler?
	    bne 	.ex11
	    bsr 	_handler	;call handler
.ex11	    move.l	D6,D0		;D0 = exception mask
	    beq 	.ex12		;we processed all exceptions
	    move.l	_QSaveExcept,A0 ;somebody else owns some exceptions
	    move.l	(sp)+,A1        ;restore exception data pointer
	    ;;add.w	  #1,_QLevel
	    jmp 	(A0)            ;call him with remaining exceptions.
.ex12	    addq.l	#4,sp		;from push at top
	    ;;add.w	  #1,_QLevel
	    rts

#endasm

/*
 *  Exception Queue handler!
 *
 *  Note:   The places I set QInHan may appear to be strange, but keep
 *	    in mind that exceptions will not occur while we are Forbid()n.
 */

static
void
handler()
{
    register QINT *qint;
    register char savepri;

    QInHan = 1;
    Forbid();
    while ((qint = (QINT *)QList.lh_Head) != (QINT *)&QList.lh_Tail) {
	if (qint->Node.ln_Pri <= QPri)  /*  priority not high enough    */
	    break;
	savepri = QPri; 		/*  save old priority		*/
	Remove(qint);                   /*  remove from queue           */
	qint->Node.ln_Type = 0; 	/*  mark as such		*/
	QPri = qint->Node.ln_Pri;	/*  up the priority		*/
	Permit();
	(*qint->vector)(qint->arg);     /*  call handler                */
	QPri = savepri; 		/*  restore priority		*/
	if (qint->vector)               /*  reenable if still exists    */
	    SetExcept(qint->sigmask, qint->sigmask);
	Forbid();
    }
    QInHan = 0;
    Permit();

    /*
     *	Exceptions are not checked on return, so we must call SetExcept()
     *	here to 'force' a check (i.e. the exception signal comes in before
     *	the exception handler returns).
     */

    SetExcept(0,0);
}

static
void
QInit()
{
    NewList(&QList);
}

/*
 *	    ----------------------------------------------------------
 *
 *  SETQVECTOR()
 *
 *  Vector a signal at a specified priority.  Setting the vector to NULL
 *  Removes the Q Interrupt.  Exceptions are automatically enabled or
 *  disabled for the signal bit.
 */

FPTR
SetQVector(signo, vector, arg, pri)
FPTR vector;
long arg;
long signo;
char pri;
{
    register QINT *qint = QInt + signo;
    register long sigmask = 1 << signo;
    register FPTR oldvector;

    Forbid();
    if (!QList.lh_Head)                         /*  Init (first call)   */
	QInit();
    oldvector = qint->vector;
    if (!vector) {                              /*  kill the Q int      */
	if (oldvector) {                        /*  one less            */
	    SetExcept(0, sigmask);              /*  disable exception   */
	    if (qint->Node.ln_Type == 5) {      /*  If queued to go     */
		Remove(qint);                   /*   Remove from queue  */
		qint->Node.ln_Type = 0;
	    }
	    if (--QInts == 0)                   /*  No more Q Ints      */
		FindTask(NULL)->tc_ExceptCode = QSaveExcept;
	}
    } else {
	if (!oldvector) {                       /*  New Interrupt?      */
	    if (++QInts == 1) {                 /*  First Interrupt?    */
		QSaveExcept = FindTask(NULL)->tc_ExceptCode;
		FindTask(NULL)->tc_ExceptCode = (APTR)except;
	    }
	}
	qint->arg = arg;
	qint->Node.ln_Pri = pri;		/*  set priority	*/
	qint->sigmask = sigmask;		/*  set signal mask	*/
	SetExcept(sigmask, sigmask);            /*  enable exception    */
    }
    qint->vector = vector;			/*  set vector		*/
    Permit();                                   /*  reenable exceptions */
    SetExcept(0,0);                             /*  force mask check    */
    return(oldvector);
}

/*
 *  SETQPRI()
 *
 *  Set the task's Q priority
 *
 *  Note:  I *could* use a Forbid()/Permit() pair, but due to the
 *  exception handling bug noted above, I would then have to do a
 *  SetExcept(0,0).
 */

char
SetQPri(newpri)
char newpri;
{
    char oldpri;

    if (!QList.lh_Head)     /*  Init (first call)   */
	QInit();
    Disable();              /*  Modify the priority */
    oldpri = QPri;
    QPri = newpri;
    Enable();

    /*
     *	Don't bother calling the handler unless there is something
     *	queued.
     */

    if (QList.lh_Head != (APTR)&QList.lh_Tail)
	handler();

    return(oldpri);
}

