[comp.sys.amiga.tech] prioritized TASK interrupts here it is!

dillon@CORY.BERKELEY.EDU (Matt Dillon) (04/14/88)

	Ok... remember, I haven't extensively tested this and it isn't
	incredibly optimized at the moment.
	
	Please report any bugs found:

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	qint.doc
#	qint.c
#	test.c
# This archive created: Wed Apr 13 20:40:22 1988
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'qint.doc'" '(4986 characters)'
if test -f 'qint.doc'
then
	echo shar: "will not over-write existing file 'qint.doc'"
else
cat << \!Funky!Stuff! > 'qint.doc'

			   QINT DOCUMENTATION

	    Matthew Dillon

	    dillon@ucbvax.berkeley.edu	ARPA
	    ...!ihnp4!ucbvax!dillon	USENET

NOTE!!!!!   Lattice Users must replace the 'jsr _geta4' call with the
	    appropriate call to retrieve the address register for the
	    small data model before compiling!	You might have to make
	    other modifications as well (I don't know since I don't
	    have Lattice).


The Calls:
	char oldpri;	    range -128 to 127
	char newpri;
	char pri;
	long signum;	    0 .. 31

	void (*vector)();   function vector returning nothing
	void (*oldvec)();


    While active Q interrupt vectors exist, the tc_ExceptCode in the
    task structure will be modified.  The old tc_ExceptCode is used if
    an unknown exception occurs (one that is not a Q interrupt).

    oldpri = SetQPri(newpri)

	Set the task's current Q priority.  Any Q interrupts of lower or
	equal priority that occur will be queued until the priority is
	dropped sufficiently.

	The initial task priority is -128 (essentially allowing all Q
	interrupts -127 to 127 to occur).

    oldvec = SetQVector(signum, vector, arg, pri)

	If vector is non-null, enables the exception at the specified
	priority (-127 to 127).  specified vector is called in a C
	compatible way with one user argument (arg).  Specifying a
	priority of -128 does not make sense because this is the lowest
	allowed priority and the Q interrupt will thus never occur.

	If vector is null, the exception and Q interrupt is disabled.
	After the last Q interrupt is removed, tc_ExceptCode is restored
	to its original value.


BUGS:
	The only bug that I know of is a problem with EXEC.

	What is Good:	An exception will not occur while one is Forbid()n
	What is Bad:	If an exception comes in while Forbid()n, it will
			NOT be immediately entered when you Permit().

	Whoops.  The exception *will* occur when EXEC next checks its
	signals and exceptions, which occurs on the obvious EXEC library
	calls (SetExcept(), SetSignal(), etc...)  and perhaps Wait().

	In most cases you can ignore the problem.


GENERAL WORKINGS OF Q INTERRUPTS:

	If you know how EXEC signals and the 68000 interrupt structure
	works, then you know how Q interrupts work.  Simply replace
	"processor" with "task" and "0-7" with "-128 to 127" for task
	Q interrupt priorities (this is different from the Task's
	scheduler priority).

	Q interrupts work just like 68000 interrupts.  If the task is
	currently running at a Q interrupt priority N, only Q interrupts
	of HIGHER priority can occur.  Q interrupts of LOWER OR EQUAL
	priority are queued and will occur as soon as the priority is
	lowered.  Everything occurs in priority order, the highest priority
	pending interrupt is always the one running (or the task's main
	routine if it has the highest priority).

	Thus, while a Q interrupt handler is running at some specific
	priority, other Q interrupts at the same priority will wait
	until the first one finishes and then execute in a FIFO fashion.

THE INTERRUPT VECTOR ROUTINE:

	A specific Q interrupt vector is a C subroutine called with one
	longword argument (that specified when you SetQVector()'d it).
	This works fine with the small model.

	The handler runs with all normal EXEC processing enabled, and in
	the context of its task.  It can be viewed almost as a subroutine
	call from the task.  However, you must be careful about the
	reentrancy of certain functions.  STDIO, DOS, and many other
	library calls are not reentrant, and you must use SetQPri() to
	ensure such calls are not interrupted.	NOTE that you CAN mix
	certain calls.	I don't have much info on what combinations work,
	but certainly most library calls that do not depend on being
	called singularly from tasks will work (for example, GetMsg()).
	And, of course, if all your handler does is make some small
	calculations this can interrupt anything.

	If you cause an exception (the same exception) from within the
	handler, it will be remembered.  (That is, the signal is cleared
	before the handler is called), and occur after your handler
	returns.

USES FOR Q INTERRUPTS:

	Use #1:     To be able to execute menu options which do not effect
		    the 'current' operation.  E.G. if you are doing a
		    ray tracing you could make the intuition window's
		    signal bit a Q interrupt and handle Intuition messages
		    that way....   The main loop generating the ray
		    tracing would not have to continuously check for
		    Intuition messages.

	Use #2:     Lets say you are writing a terminal program and want
		    to display data comming in smoothly while sending a
		    file.  While this can be done easily with the
		    asynchronous ability of IO devices, it would be even
		    easier if you handled the receive with a Q interrupt...
		    that way, you could display received data (SendIO to
		    the console device) even if the file sender is in
		    the middle of a DOS call to read the next file block.

	Many more uses (I hope!).

			    -Matt


!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'qint.c'" '(8360 characters)'
if test -f 'qint.c'
then
	echo shar: "will not over-write existing file 'qint.c'"
else
cat << \!Funky!Stuff! > 'qint.c'

/*
 *  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);
}

!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'test.c'" '(1824 characters)'
if test -f 'test.c'
then
	echo shar: "will not over-write existing file 'test.c'"
else
cat << \!Funky!Stuff! > 'test.c'

/*
 *  QINT TEST ROUTINE
 *
 *  1> test
 *	    Ctl D   -quit
 *	    Ctl E   -cause test interrupt #1
 *	    Ctl F   -cause test interrupt #0
 *
 *	    (see printf's to understand what it is printing)
 *
 *	Basic idea: Cycle through various priorities.  The Q interrupts
 *	are both set for priority 16, and thus only work when the counter
 *	is 0...15
 *
 *	If you set one of the Q interrupts to, say, 17, you would then
 *	have to worry about the level 17 Q interrupt interrupting the
 *	level 16 interrupt, and thus surround the level 16 interrupt
 *	with:
 *	    char oldpri = SetQPri(127);
 *	    puts("blah");
 *	    SetQPri(oldpri);
 *
 *	As it is, with both at level 16, they cannot interrupt each other
 *	(if one occurs while the other is running, it is queued till the
 *	 other one finishes).
 */

#include <exec/types.h>
#include <exec/tasks.h>

typedef struct Task TASK;

extern TASK *FindTask();

int X;
int Enable_Abort;

void
myhan(sig)
{
    ++X;
    printf("test# %ld\n", sig);
}

main()
{
    short i;
    int j;

    Enable_Abort = 0;

    printf("tcexcept: %08lx\n", FindTask(NULL)->tc_ExceptCode);
    SetQVector(SIGBREAKB_CTRL_F, myhan, 0, 16);
    SetQVector(SIGBREAKB_CTRL_E, myhan, 1, 16);
    printf("tcexcept: %08lx\n", FindTask(NULL)->tc_ExceptCode);
    printf("tcexmask: %08lx\n", FindTask(NULL)->tc_SigExcept);
    for (i = 0; ; ++i) {
	i &= 31;
	SetQPri(127);
	printf("i = %2ld rcvd: %08lx  excp: %08lx  cnt: %6ld\n", i,
	    FindTask(NULL)->tc_SigRecvd, FindTask(NULL)->tc_SigExcept,
	    X
	);
	SetQPri(i);
	for (j = 0; j < 100; ++j);
	if (SetSignal(0,SIGBREAKF_CTRL_D) & SIGBREAKF_CTRL_D)
	    break;
    }
    SetQVector(SIGBREAKB_CTRL_E, NULL, 0, 0);
    SetQVector(SIGBREAKB_CTRL_F, NULL, 0, 0);
    puts("exiting");
    printf("tcexcept: %08lx\n", FindTask(NULL)->tc_ExceptCode);
}

!Funky!Stuff!
fi  # end of overwriting check
exit 0
#	End of shell archive

dillon@CORY.BERKELEY.EDU (Matt Dillon) (04/15/88)

	Oh, I forgot to mention... compile the program with 32 bit integers!

				-Matt