Trailing-Edge - PDP-10 Archives - decuslib10-07 - 43,50433/paspsi.doc
There are 2 other files named paspsi.doc in the archive. Click here to see a list.
This document describes a set of routines for using the PSI system in PASCAL.
The user should read the section on PSI in the Monitor Calls Manual before
trying to use this facility, although this file, especially the examples,
may give those who haven't read it some idea of the facilities, at least.

To use these routines, external declarations must be included in the
user's program.  Also, the routines themselves must be loaded with
the program.  Currently this requires that you load the file
AIL:PASPSI.REL with your program.

Warning:  I have tried simple programs, but this is not well debugged.
If anyone finds a use for it, please tell me what you are doing and I
will help you, since some final debugging may be needed.

Later note:  It turns out that you are far more likely to find a monitor
bug having to do with PSI, HIBER, etc., than you are to find a bug in
my routines.  The PSI system is rotten.  (I mean that not as a meaningless
insult but seriously:  it is full of places that will fall in on you if
you step on them.)

The PSI system has a number of conditions on which an interrupt can
occur.  These include ^C, illegal memory reference, and various
I/O conditions (e.g. transfer done, disk full).  To establish an
interrupt condition, three things must be done:
  (1)	You must initialize the PSI system.  This is done with PSIINI.
	It uses the PIINI. UUO to initialize PSI and give the monitor
	a "vector" which will contain an entry for each condition
	you establish.  However, initializing the PSI system does
	not turn it on. Thus,
  (2)	You must turn on the PSI system.  This is done with PSION.
	It uses the PISYS. UUO to turn on PSI.  Note that this must
	be done after the PSIINI, and that it is in a sense less
	permanent.  That is, you may establish conditions and then
	keep turning them on and off with PSION and PSIOFF.  PSION
	will restore the handling of interrupts which was suspended
	by PSIOFF.  PSIINI does a complete reinitialization (i.e.
	it cancels all conditions).
  (3)	Finally, you must establish the conditions on which an
	interrupt will be taken.  This is done with PSIADD.  PSIADD
	uses the PISYS. UUO to add the specified condition.  It also
	takes the location of a PASCAL procedure as an argument and sets
	things so that that procedure will be called to process the
	interrupt.  Note that in the default case this procedure
	is executed at interrupt level, and any other interrupts will
	be held until it is finished.  This can be overriden, but
	you will have to be very careful indeed if you do so.
		PSIADD returns an "index".  This is needed as a unique
	way to refer to the condition for purposes of cancelling it
	(or other nefarious functions that may be added later).
	Note that there are two different ways to cancel an interrupt
	added by PSIADD:  PSIOFF just suspends interrupt processing
	of all interrupts.  The interrupts are still set, and processing
	will be resumed at the next PSION.  PSIREM(index) removes
	the one particular interrupt condition referred to.  It is
	as if it had never been there.  (Thus if you establish an
	interrupt on ^C, PSIOFF causes ^C interrupts to be delayed
	until the next PSION, but does not let ^C stop your program.
	PSIREM would restore normal ^C behavior.)
		I/O interrupts are a special case.  For them, you
	specify not only the interrupt type (this would be the device,
	I/O channel, or UDX), but also a "reason".  This might be
	disk full, etc.  It is only possible to have one condition
	(i.e. one index value) for a given device, but that condition
	may specify as many reasons as you like.  When an interrupt
	happens you will be able to find out which reason caused it.
	To establish multiple reasons, you simply set several bits
	in the reason argument of the PSIADD.  Do not try to do more
	than one PSIADD for the same device, as the monitor will
	ignore all but the first.  (To add reasons you will have to
	do PSIREM and then PSIADD with all the reasons including the
	new ones.)
		Note that the monitor does not return the type
	which caused an interrupt.  That is why we are enforcing
	a restriction of using a separate index for each type.
	However it when the type is an I/O device, you are told
	which reason caused the interrupt, and we pass this information
	on to your procedure.  (We also pass the index of the
	condition, in case the same procedure is handling more than

When the interrupt-handler passes control to the routine you specified,
certain information is present in accumulators.  These will show up
as parameters to your interrupt procedure.  Thus I recommend making
the interrupt procedure a procedure with four parameters:
	arg 1 - index - this is the index of the condition, as
			returned by the PSIADD when you added it

	arg 2 - PC - this is where the main program was when the
			interrupt happened

	arg 3 - reasons - This is the reason field (right half of
			.PSVFL) from the vector.  It is useful for
			I/O interrupts, to tell you which of the
			enabled reasons caused the interrupt.
	arg 4 - status - definition depends upon the interrupt.  See the
		monitor calls manual.  This is the status word (.PSVIS)
		from the vector when the interrupt occurred.

When designing a program that will use PSI, you must constantly keep
in mind the fact that an interrupt can occur anywhere.  Thus there can
be subtle timing problems.  For example, if you an interrupt occurs
while you are waiting for TTY input, and the interrupt procedure
also asks for TTY input, it will be very hard to unscramble the input.
I do save and restore the status of the TTY file control block around
interrupts, but that still may give strange results.  Note that an
interrupt processor should never use the same file as the main program,
except for TTY.  Terrible, subtle bugs will result.

The other known limitation on what you can do in interrupts has to do
with NEW and DISPOSE.  If you don't use DISPOSE in your program, there
is no problem.  Programs that don't use DISPOSE get a simple version of
NEW that is designed to be reentrant.  When you use DISPOSE (even once),
we give you a full-scale dynamic memory manager.  This memory manager is
not reentrant.  So if you interrupt NEW or DISPOSE and do another NEW or
DISPOSE at interrupt level, the heap will probably get corrupted.  There
is no problem if any of the following 3 things is true:
  - you don't use DISPOSE, because then you get a reentrant memory
  - you don't use NEW or DISPOSE at interrupt level
  - you know that an interrupt can never occur during NEW or DISPOSE
	(e.g. you are handling floating-point overflow via interrupts,
	since the memory manager is guaranteed not to produce floating
	point overflows!)
Otherwise, you will have to contrive to treat calls to NEW or DISPOSE as
critical sections.  (Unfortunately I have not  implemented critical
sections for you on Tops-10.  I have on Tops-20, but would need a test
site before I could do it on Tops-10.  I have no way to test Tops-10
interrupt handling code.)

Note that there is no way to predict the lexical context in which the
interrupt processor routine will be called.  Thus it is not possible to
set up the lexical display correctly.  This means that such routines
must refer only to their own local variables and variables declared at
the outermost level of the program.  They may not refer to anything at
an intermediate level.  This restriction cannot be enforced, but
violating it will lead to strange results.  Note that you may often set
a global flag to indicate that an interrupt occurred, and let the main
program handle it when it notices.

index _ psiadd(type,reason,procedure)
	This is the simplest form of the PSIADD.  It adds a condition
	whose type is specified by type.  This is either an I/O
	device, channel, or UDX, or it is a negative number specifying
	some non-I/O type (e.g. -3 is ^C).  For an I/O type, you
	must also specify the reason, as explained above.  This is
	a half word of bits.  For non-I/O types, reason must be 0.
	Procedure should probably be a global procedure, i.e. defined
	at the outer level of the program.

index _ psiadd(type,reason,procedure,control)
	This is the full form of PSIADD.
	Control is for the brave soul who wants special effects
	from the PSI system.  With it you can specify that when an
	interrupt occurs the PSI system should be left running even
	during processing of the interrupt.  Or you can say that it
	should be turned off until explicitly reenabled with PSION.
	See the Monitor Calls manual for the bits to use for these
	things.  (Note that they should be put in the right half
	word of control.  PSIADD will move them to the left half
	of .PSVLF.)  Control defaults to PS.VTO, which holds all
	interrupts until the immediate processing routine finishes.
	(This is the procedure passed to psiadd, either your immediate
	interrupt routine, or PSIDFR.)  This is probably the only
	sane choice.  Note that the PSI interrupt handler is not
	reentrant.  If you override our default and allow other
	interrupts to happen during processing of an interrupt, the
	interrupt handler will get confused.

	As explained above, this turns on processing of interrupts.
	It may be done before or after PSIADD.  Of course it has
	no effect until at least one condition has been added.
	PSION is illegal until a PSIINI has been given.

	As explained above, this suspends processing of interrupts,
	but does not change which conditions have been added.

	Removes a condition set up by PSIADD.  Frees its index
	entry for use by someone else (since there are a limited
	number of entries in the tables).

	Clears any pending interrupts for the condition referred
	to by index.  The condition is still enabled (assuming
	PSIOFF isn't in effect), you just clear it out.  I'm not
	sure that this is useful for very much.  DEC says you might
	use it in an interrupt level routine if it caused more interrupts
	which you wanted to ignore.

	Must be done before any other PSI function.  If it is done
	again in the middle of a program it will cancel all interrupts
	and completely clear out the system.  PSION and at least one
	PSIADD will be needed again before anything else can happen.

A typical use of PSI is shown below.  Assume PASCAL did not tell you
when an arithmetic exception (e.g. divide by zero) happened.  (In
fact the runtimes go to some considerable pain to diagnose these
arithmetic exceptions, but this is still a useful example.)  We will
set up a trap for them and print an appropriate message.  Since you
may get tired of seeing the same message, the interrupt is removed
after four times.

program psidemo;
var index:integer;
function psiadd(a,b:integer;procedure c;d:integer):integer;extern;
procedure psiini; extern;
procedure psion; extern;
procedure psirem(i:integer);extern;
procedure trapper(index,pc,reason,status:integer);
 writeln(tty,'% Arithmetic exception at user PC ',PC-1:6:O);
 if count>3 then psirem(index);
index:=psiadd(-10b,0,trapper,0);  %-10b is the code for arithmetic
while true do i:=trunc(1.0/0.0);	%This will trigger it, since it
					 divides by zero\