Interrupts, %EVENTs and fault trapping.
Interrupts
Subsystem cannot control or handle interrupts on its own, and has to
exploit the facilities offered by Director for the purpose.
PRIME CONTINGENCY
One of the tools provided is the
%EXTERNAL %INTEGER %FN PRIME CONTINGENCY (%ROUTINE TRAP)
in Director. This need only be called once, to nominate the routine TRAP
to Director. The %SPEC of TRAP must be
%ROUTINE %SPEC TRAP (%INTEGER CLASS, SUBCLASS).
If an interrupt subsequently occurs which is relevant to the process
(and subject to some conditions which will appear later), Director
will suspend the process and force it to enter TRAP, supplying the
two %INTEGER parameters CLASS and SUBCLASS to indicate what sort of
interrupt has occurred. TRAP will normally be some routine which is
declared as part of the subsystem, and in the standard subsystem it is
the routine DIRTRAP, declared in module BASE. The corresponding call of
PRIME CONTINGENCY is almost the first instruction executed in the
%SYSTEM %ROUTINE CONTROL declared in module CONT.
READID
Within the TRAP routine, the subsystem can identify the kind
of interrupt from the values of CLASS and SUBCLASS, and can find more
details by calling the
%EXTERNAL %INTEGER %FN READID (%INTEGER AD)
which is part of Director (see Director documentation for details), and a
suitable call is found quite early in DIRTRAP. The parameter AD must be
the address of the first element of an array of 18 words.
Environment
The data returned by READID will be the 'environment' at the time
of the interrupt, laid out as follows:
1: SSN/LNB
2: PSR
3: PC
4: SSR
5: SSN/SF
6: IT
7: IC
8: CTB
9: XNB
10: B
11 and 12: DR
13 to 16: Accumulator
17: FPC (for PE only)
18: ?
DRESUME
When the subsystem has finished processing the interrupt, if it is
required to resume the process at the point where it was suspended before
Director forced the call of the TRAP routine, then it must call
DRESUME (0, 0, AD), where DRESUME is supplied in Director with the
specification
%EXTERNAL %ROUTINE %SPEC DRESUME (%INTEGER LNB, PC, AD)
and AD has the same value as the AD parameter passed to READID (or,
at any rate, is the address of 18 words containing the same data as
was returned from READID). There is such a call of DRESUME near the end
of DIRTRAP, just before the lable REROUTE.
Inhibiting interrupts
SSINHIBIT and SSINTCOUNT
From time to time, Subsystem may find it impractical to handle
interrupts for a short period, and it can inhibit calls on DIRTRAP by
setting a non-zero value into the first of two consecutive words which
are declared as SSINHIBIT and SSINTCOUNT in the Subsystem. So long as
SSINHIBIT is non-zero, Director will not force any calls on DIRTRAP,
even if some interrupt occurs; but if one does occur, details will
be recorded in Director's tables for future reference, and SSINTCOUNT
will be incremented.
ALLOW INTERRUPTS
Later on (but, for safety's sake, after only a short
delay), Subsystem will clear SSINHIBIT to zero, to allow calls on DIRTRAP
again. Once SSINHIBIT is zero, all the interrupts which arrived
while calls of DIRTRAP were inhibited, and whose details have been
saved up by Director, must be processed: this is achieved by
%WHILE SSINTCOUNT>0 %THEN I = DASYNCINH (1,0)
which requests Director to initiate the appropriate calls of
DIRTRAP (supported by the relevant information to be acquired
via READID) for each of the 'pending' interrupts, one after another.
It is left to Director to decrement SSINTCOUNT. The relevant code in
Subsystem is found in the %SYSTEM %ROUTINE ALLOW INTERRUPTS (with
no parameters) in the BASE module.
DASYNCINH
For this mechanism to work, Director must know where to find
the two consecutive words SSINHIBIT and SSINTCOUNT. This information
is supplied by Subsystem in a call on DASYNCINH (0,ADDR(SSINHIBIT));
the declaration of DASYNCINH is
%EXTERNAL %INTEGER %FN %SPEC DASYNCINH (%INTEGER MODE, A)
and the relevant call is found near the start of the routine CALL BCI
in CONTROL in module CONT. Until this call has been made, Director will
not initiate any calls on DIRTRAP, even though DIRTRAP has already been
nominated by PRIME CONTINGENCY. Instead, all interrupts will be 'saved'
or kept pending from the time of the call of PRIME CONTINGENCY until the
call on DASYNCINH. Any interrupts which were already pending at the
time of the original call on PRIME CONTINGENCY, on the other hand, will
be discarded and utterly lost. Consequently the start-up sequence of the
Subsystem includes a very early call on PRIME CONTINGENCY, but defers
the first call of DASYNCINH until much later, when the environment is
completely set up for handling interrupts. Only after that call will
Director initiate calls on DIRTRAP for the pending interrupts (if any).
Calls on Director
Apart from declarations, Subsystem contains one call of READID,
one use of DIRTRAP, two calls of DASYNCINH and two of DRESUME. We have
so far accounted for all of these except one call on DRESUME. The
story so far is a complete account of the interactions between Subsystem
and Director for processing interrupts: what follows is concerned
with Subsystem's internal mechanisms and with the effects on, and
facilities for, user programs.
REROUTE CONTINGENCY
Director is not very selective about interrupts. If it has
something to report to Subsystem, then the only decision which arises
is whether to report it now or to save it for later. There are no
facilities to inhibit some kinds of interrupts but not others, nor to
report different kinds of interrupts through different paths. (It is
possible for Subsystem to change the nominated TRAP routine by a second
call on PRIME CONTINGENCY, but this is not a very versatile facility, and
Subsystem soes not use it.) However, Subsystem has an internal mechanism
to achieve some of these things. There is a
%SYSTEM %ROUTINE REROUTE CONTINGENCY (%INTEGER EP, CLASS, %C
%LONG %INTEGER MASK, %ROUTINE STRAP, %INTEGER %NAME FLAG)
(declared in BASE) which allows the caller to nominate an alternative routine
STRAP with specification
%ROUTINE %SPEC STRAP (%INTEGER CLASS, SUBCLASS)
exactly like the TRAP parameter to PRIME CONTINGENCY. The parameters EP,
CLASS and MASK in a call of REROUTE CONTINGENCY indicate which kinds of
interrupt are to be handled by STRAP. Up to eight different STRAP routines
can be simultaneously defined, by different calls on REROUTE CONTINGENCY,
each one for a different kind of interrupt. Director will still initiate
a call of DIRTRAP for any kind of interrupt, but DIRTRAP contains code
to select the appropriate STRAP routine and initiate a call on that. If
the interrupt is of a kind for which no STRAP has been nominated, then
DIRTRAP will handle it in the normal way. Within a STRAP routine,
READID may be used to obtain complete information about the interrupt,
and DRESUME can be used to resume the interrupted process, just as
is done in DIRTRAP.
REROUTE CONTINGENCY can be used to define up to 8 different STRAP
routines to handle different kinds of interrupt (actually, the limit
is the declared value of the %CONST %INTEGER MAXRRC). The number of
STRAP routines currently defined is given by the %INTEGER RRCTOP, and
the whole set of STRAP routines can be discarded by calling
REROUTE CONTINGENCY with the parameter EP=0 (which has the effect of
setting RRCTOP to zero). Other calls on REROUTE CONTINGENCY must have
1<=EP<=5, and these are used to nominate STRAP routines. EP, CLASS
and MASK are used to specify what kinds of interrupt should be
handled by STRAP. MASK is a 64-bit mask in which the least significant
(right-hand) bit corresponds to the value 0 or 64, the next bit
corresponds to 1 or 65, and so on up to the most significant
(left-hand) bit which corresponds to 63 or 127. The meaning
of the values of EP, CLASS and MASK are as follows:
N.B.: In the following table, 'class' and 'subclass' (written in
lower case) refer to the properties of an interrupt. 'CLASS'
and 'MASK' (written in upper case) refer to the parameters
supplied to REROUTE CONTINGENCY.
EP=1 Handle interrupts whose class = CLASS (ignore MASK).
EP=2 Handle interrupts whose class = CLASS, provided that
the bit corresponding to subclass is set in MASK
(for subclasses 0 to 63).
EP=3 Handle interrupts whose class = CLASS, provided that
the bit corresponding to subclass is set in MASK
(for subclasses 64 to 127).
EP=4 Check the MASK bit corresponding to the class
of the interrupt, and handle the interrupt if the bit
is non-zero (for classes 0 - 63). Ignore CLASS.
EP=5 Check the MASK bit corresponding to the class
of the interrupt, and handle the interrupt if the bit
is non-zero (for classes 64-127). Ignore CLASS.
Incidentally, any use of EP=1 could be replaced by an equivalent
use of EP=4 or 5 (unless one is dealing with interrupt classes greater
than 127).
DIRTRAP
REROUTE CONTINGENCY is, in fact, used nowhere in the standard Subsystem.
Consequently all interrupts are handled by DIRTRAP, so we must now
consider what DIRTRAP does. For completeness, this description will
include 'forward references' to some things which have not yet been
defined - in particular, SIGLEVEL and SIGNAL - but the general
outline should be sufficiently clear if you know that SIGNAL is a
routine in the Subsystem which is used to notify the 'user program'
of an event.
1. Check whether there is any STRAP routine nominated by a previous
call of REROUTE CONTINGENCY to handle this interrupt. If there is,
bodge up a call to that routine, passing on the parameters CLASS
and SUBCLASS. There will be no further action in DIRTRAP. In
the usual case, there is no STRAP routine, so DIRTRAP carries on
at step 2.
2. Check 0<SIGLEVEL<=MAXSIGLEVEL. This is simply a check that
SIGNAL will be able to function if required. DSTOP(103)
if the check fails.
3. READID to get the interrupt data.
4. Bodge the PSR field of the return link from DIRTRAP (at LNB+1)
so that it looks as if DIRTRAP was called directly from the
interrupted process. (This is said to be for the sake of ONCOND
in NDIAG).
5. Action depends on the class of interrupt.
Most interrupts
CLASS # 64, 65 or 66 - i.e., action for most interrupts:
if STOPPING then DSTOP (113);
copy the interrupt data (as returned by READID)
into SIGDATA(SIGLEVEL);
copy the same data, PLUS class, subclass and time, into
one of the SAVEIDATA areas (for #REGS);
SIGNAL (2,CLASS,SUBCLASS,FLAG) - i.e., signal the event
at the most recently defined level;
resume.
Time exceeded
CLASS = 64 (time limit exceeded):
use DSETIC to get some more time;
if STOPPING then resume with no further action;
copy the interrupt data (as returned by READID)
into SIGDATA(SIGLEVEL);
copy the same data, PLUS class, subclass and time, into
one of the SAVEIDATA areas (for #REGS);
SIGNAL (2,64,SUBCLASS,FLAG) - i.e., signal 'time
exceeded' at the most recently defined level;
resume.
Single character INT
CLASS = 65 (single character INT:) - SUBCLASS will be the
interrupt character:
convert lower to upper case;
convert all characters to 'X' for batch jobs;
recognise which character;
if it's not a recognised character (Q,W,X,Y,T,A,C),
then resume with no further action;
W, X, Y
for W, X, Y:
if STOPPING then DSTOP (113);
set INT IN PROGRESS (ignoring its previous value);
SIGNAL (3,65,SUBCLASS,FLAG) - i.e., signal at outermost level;
resume.
A, C
for A, C:
if INT IN PROGRESS or STOPPING then resume (i.e., ignore it);
set INT IN PROGRESS;
SIGNAL (3,65,SUBCLASS,FLAG) - i.e., signal at outermost level;
resume.
Q
for Q:
if INT IN PROGRESS or STOPPING then resume (i.e., ignore it);
set INTQ (but INT IN PROGRESS is not set - why not?);
copy the interrupt data (as returned by READID)
into SIGDATA(SIGLEVEL);
copy the same data, PLUS class, subclass and time, into
one of the SAVEIDATA areas (for #REGS);
SIGNAL (2,65,'Q',FLAG) - i.e., signal 'INT:Q'
at the most recently defined level;
resume.
T
for T:
if INT IN PROGRESS or STOPPING then resume (i.e., ignore it);
CONSOLE (12,FLAG,FLAG) to print the time, etc.;
resume.
Operator message
CLASS = 66 (operator message):
if not STOPPING then call CONSOLE (6,...) to print the message;
resume.
SIGNAL
The routine SIGNAL is declared in module BASE in the Subsystem as
%SYSTEM %ROUTINE SIGNAL (%INTEGER EP, P1, P2, %INTEGER %NAME FLAG).
It uses the integer SSCOMREG(34) which is often referred to by the
%INTEGER %NAME SIGLEVEL
and the other relevant declarations are
%CONST %INTEGER MAXSIGLEVEL = 7
%RECORD %FORMAT SIGDATAF (%INTEGER PC, LNB, CLASS, SUBCLASS, %C
%INTEGER %ARRAY A (0:17))
%OWN %RECORD %ARRAY SIGDATA (1:MAXSIGLEVEL) (SIGDATAF)
Specification
SIGLEVEL must satisfy 0<=SIGLEVEL<=MAXSIGLEVEL.
From 0 to 7 (i.e., MAXSIGLEVEL) 'levels' of event handling may be defined.
SIGLEVEL indicates how many are actually defined. For each defined level,
SIGDATA(I)_PC and SIGDATA(I)_LNB give the values of PC and LNB for
restarting the process when an event occurs. Levels are defined by calling
SIGNAL with the parameter EP=0 (or -1 or 9) and the values for PC and
LNB as parameters P1 and P2. The first such call on SIGNAL defines
level 1: SIGLEVEL must be zero beforehand, and will be 1 on return from
SIGNAL(0,...). Subsequent calls define levels 2, 3, and so on, and of
course each call increments SIGLEVEL, so that SIGDATA(SIGLEVEL) is
always the most recently defined level.
A level may be 'abandoned' by calling SIGNAL with EP=1 and P1=0; this
simply decrements SIGLEVEL. This is a necessary precaution on exit from
the routine or lexical level where the SIGNAL(0,...) call occurred,
since the stack frame is discarded and the LNB value becomes meaningless.
Calling SIGNAL with EP=1 and P1#0 will set SIGLEVEL = 0, thus discarding
all levels: this is a sensible precaution before the first call of
SIGNAL(0,...), and this sequence is found near the beginning of CALLBCI
in CONTROL in module CONT.
When some event occurs, SIGNAL(2,...) or SIGNAL(3,...) may be used to
'signal' the event: that is, to resume execution of the process using
the PC and LNB extracted from one of the SIGDATA records. This
resumption is achieved by
DRESUME (SIGDATA(I)_LNB, SIGDATA(I)_PC, ADDR(SIGDATA(I)_CLASS))
which effectively loads the LNB and passes control to the code at PC,
with a copy of the third parameter in the accumulator (see documentation
of DRESUME for further explanations). SIGNAL(2,...) uses I=SIGLEVEL and
decrements SIGLEVEL: i.e., it signals the event at the most recently defined
level (excluding, of course, levels which have already been abandoned),
and abandons that level so that it will not be used again for subsequent
events. SIGNAL(3,...), on the other hand, uses I=1. This has the
effect of resuming execution at the 'outermost' level (i.e., the
level first defined).
Before calling SIGNAL(2,...), it is useful to fill in the contents
of the array SIGDATA(SIGLEVEL)_A with information about the event.
The routine SIGNAL itself will copy its own parameters P1 and P2
into SIGDATA(SIGLEVEL)_CLASS and _SUBCLASS. The fields CLASS,
SUBCLASS and A then constitute 80 bytes of information about the
event, and, as we have seen, the address of this block of data is
supplied as a parameter to the call of DRESUME, and it ends up in the
accumulator when the process is resumed, so that the process can
determine what event has occurred. In DIRTRAP, _A gets a copy of
the 18-word 'environment' as delivered by READID before the call of
SIGNAL. It would also be possible to fill in SIGDATA(1)_A before a
call of SIGNAL(3,...), because SIGNAL passes the data on in the same
way, but as far as I know this is not actually done in the standard
subsystem.
SIGNAL offers an extra facility: a call of SIGNAL(4,...) will 'repeat'
the last call of SIGNAL(2,...) or SIGNAL(3,...). That is to say, it
will simulate a call of SIGNAL(2,...) using the same _CLASS, _SUBCLASS
and _A as were supplied to the last call of SIGNAL(2,...) or
SIGNAL(3,...). Now a call of SIGNAL(2,...) uses the _PC and _LNB from
SIGDATA(SIGLEVEL) to resume the process, and also decrements SIGLEVEL,
thus abandoning the level; and the simulated calls made by SIGNAL(4,...)
work the same way. Thus, repeated calls of SIGNAL(4,...) will signal
the same event at the various levels, most recently defined first.
The way to exploit this is for each level to have a PC and LNB which
will pass control to some code whose first action is to examine the
class, subclass and interrupt data (which are pointed to by the
address in the accumulator). The code must then decide whether it can
handle the signalled event. If it can, then it does; but if it
cannot, it simply calls SIGNAL(4,...) to activate the previous level,
which can do the same sort of test. In this way, an event is signalled
at successive levels until some level is prepared to cope with it.
SIGNAL(5,...) simply does %MONITOR; %STOP.
SIGNAL(6,AD,..) copies the current value of SIGLEVEL into the integer
pointed to by the address AD.
Summary
Summary of parameters:
(0,PC,LNB,FLAG) define a new 'level': save PC and LNB, increment SIGLEVEL.
(1,0,-,FLAG) abandon a level: decrement SIGLEVEL.
(1,n{#0},-,FLAG) abandon all levels: set SIGLEVEL = 0.
(2,CLASS,SUBCLASS,FLAG)
signal event at most
recently defined level
and abandon that level:
event defined by CLASS, SUBCLASS
and SIGDATA(SIGLEVEL)_A;
pass control to code with PC, LNB
extracted from SIGDATA(SIGLEVEL);
decrement SIGLEVEL.
(3,CLASS,SUBCLASS,FLAG)
signal event at outermost level:
event defined by CLASS, SUBCLASS
and SIGDATA(1)_A;
pass control to code with PC, LNB
extracted from SIGDATA(1).
(4,-,-,FLAG) signal last event again
at most recent surviving level.
(5,-,-,FLAG) %MONITOR; %STOP.
(6,ADDR(L),-,FLAG)
copy SIGLEVEL into L.
ONCOND
This note is about the routine ONCOND (in module NDIAG), which is used to
implement the IMP fault trap mechanism. Its specification is
%ROUTINE %SPEC ONCOND (%INTEGER EVENT, SUBEVENT, LNB)
It is only called once, fom the routine NDIAG. Its purpose is to unravel
the stack of an interrupted process (while running on that stack itself!)
looking for a stack frame corresponding to IMP source code which has
nominated an %ON %EVENT action for the event which has occurred. If
no such stack frame is found, ONCOND will %RETURN to NDIAG. If one is
found, then instead of returning to NDIAG, the LNB and SF will be adjusted
and ONCOND will pass control (by EXIT) to the code indicated by the
%ON %EVENT trap.
ONCOND will refuse to handle any EVENT which is outside the range 1 - 14
(i.e., it will simply %RETURN to NDIAG). Within ONCOND, EVENT numbers
are represented as bits in a word, with bit 0 (the most significant, left-
hand bit) corresponding to event number 14, and bit 13 corresponding to
event number 1.
Specification
The action of ONCOND is as follows:
1. PREVLNB gets a copy of the stack frame pointer for the level
which called ONCOND - i.e., for NDIAG.
2. If we have unravelled the stack far enough, or LNB is not in
the stack segment, then %RETURN to NDIAG.
3. Extract GLA address from the stack frame indicated by LNB.
4. Pick out the language flag from the GLA, and %RETURN if it's not IMP.
5. Look in the stack frame again for the GLA descriptor, and this time
pick out the bound from it: assign this value to TSTART.
6. TSTART is not actually a sensible bound: it is an offset within
the shareable symbol tables (not even within the GLA at all!) which
will lead you to a diagnostic record - or it's zero, if there are
no such records.
7. If TSTART is zero - i.e., no more diagnostic records - then
we must try the previous stack frame - which we find by going
on to step 13. Otherwise, we carry on with step 8.
8. Find the address of the shareable symbol tables (this is in
the fourth word of the GLA) and add it to TSTART so that
TSTART now points to a diagnostic record.
9. Locate the ONWORD within the diagnostic record: this is not
trivial, since the word is word aligned within the record, but
follows a variable length name string!
10. Using the bit-mask convention described above, check whether
the bit corresponding to EVENT is set in the ONWORD. If
it is, we have found an %ON %EVENT trap which is prepared
to handle the event which has occurred, and we carry on at step
15. If not, then we carry on with step 11.
11. If the name string in the diagnostic record (see step 9) is
null, then we are simply dealing with a %BEGIN-%END block, and
since IMP does not generate new stack frames for block entry,
we simply pick up another diagnostic record: this is found by
picking up a link from the short integer in the 7th. and 8th.
bytes of the diagnostic record, assigning that to TSTART, and
going back to step 7.
12. If the name string was non-null, then we have been dealing with
a routine, so we carry on to step 13 to try the previous stack
frame.
13. Locate the previous stack frame:
set PREVLNB = LNB and LNB = INTEGER (LNB).
14. Go back to step 2 to check whether it's a valid stack frame.
15. We have found an %ON %EVENT trap which is prepared to handle
the event. Build a double word of information about the event
containing EVENT in the top 24 bits, SUBEVENT in the next 8
bits, and the line number (extracted from the stack frame, using
the offset held in the half integer in the third and fourth words of
the diagnostic record) in the bottom word.
16. Abandon one level of SIGNAL trap (i.e., decrement SIGLEVEL by
calling SIGNAL(1,0,0,I)). This is alleged to be because NDIAG
planted a SIGNAL trap before calling ONCOND.
17. LNB now points to the stack frame for the level which set the
trap. PREVLNB points to the stack frame called immediately from
that stack frame, or to the stack frame for NDIAG if the trap
was set at the outermost level of the 'user program' or command.
18. Set ACTIVE = 0. This variable is used by NDIAG to detect whether
diagnostics are in progress when an event is signalled, and this
is associated with the testing for INTERRUPT DURING DIAGNOSTICS.
19. Fiddle the stack frame indicated by PREVLNB and EXIT so that
we resume execution:
at the code address located via ONWORD (whose bottom 18
bits are an offset within the GLA of a word which contains
the address of the ON code);
with ACS=2 and the double word describing the event in the
accumulator;
with LNB correct for the level which set the trap;
and with SF also correct (but if the trap was set at the level
at which the interrupt occurred, then SF would include everything
up to, but not including, the call of ONCOND - in particular,
it would include the stack frames for DIRTRAP, SIGNAL and NDIAG!).
Data
Data used by this mechanism:
Stack frame
1. Stack frame, located via LNB.
Word 0: pointer to previous tack frame.
Words 1 and 2: return link (code descriptor)
Word 3: first word of GLA descriptor. The bound (bits 8-31
of this word) is actually an offset within the
shareable symbol table, pointing to a diagnostic
record.
Word 4: address of GLA.
.
.
Word n: line number. n can be found from the diagnostic
record (defined below), and when there is no line
number, n will be given as zero.
GLA
2. GLA, located via word 4 of stack frame.
Words 0 to 2: ....
Word 3: address of shareable symbol tables.
Word 4: most significant byte is the language code.
.
.
Word j: address in code at which to resume processing when a
'trapped' event occurs: j is found from ONWORD in the
diagnostic record.
Diagnostic record
3. Diagnostic record, found via word 3 of the stack frame and word 3
of the GLA.
Word 0: less significant half-word is the offset in the
stack frame for the line number word (or zero if there
is no line number word).
Word 1: less significant half-word is the offset in the
unshareable symbol table for another diagnostic record -
i.e., this is the link to the next record.
Word 2: not used in this code.
Words 3 onwards: a name string, with the length byte in the
most significant byte of word 3, and the remainder
of the last word (if any) unused. If there is no
name, then Word 3 is zero.
Word i (the word immediately after the last word occupied by
the name): ONWORD, in which bits 0-13 are a mask indicating
which events can be accepted, and bits 14-31 are an
offset within the GLA of a word containing the address of
the code at which to resume processing when the event
occurs.
Event INFO
4. Double word INFO - describing an event.
Word 0: bits 0-23, event number.
bits 24-31, subevent number.
Word 1: line number of source code (or zero).
Processing an event.
Environment
The common situation when a program or command is running is this:
Subsystem has nominated DIRTRAP to Director by a call on PRIME CONTINGENCY.
It has also nominated SSINHIBIT and SSINTCOUNT by a call on DASYNCINH.
SSINHIBIT is zero.
Two 'SIGNAL' traps have been laid down.
The first (outer) level is laid down very early in routine CONTROL in
module CONT. It transfers control to label CONTIN in that routine, where
there is code to cope with INT:W,X,Y,A and C. If any other event arrives
here, it is treated as "SUBSYSTEM FAULT = CLASS, SUBCLASS".
The second (inner) level is laid down either in routine BCI (in module
CONT), or in routine ENTER ON USER STACK (in module LOAD). In each case,
this trap takes us to the label FAIL in the routine which set the trap,
where the following sequence occurs:
*ST_DESCR0 [DESCR0 and DESCR1 are consecutive words]
NDIAG (INTEGER(DESCR1+16), INTEGER(DESCR1+8), 10, INTEGER(DESCR1))
Action
When Director is activated to handle some event, it initiates a call on
DIRTRAP, on the same stack where the process was working when the event
occurred. DIRTRAP uses READID to get 18 words of interrupt data, and, in
most cases, stores it in the appropriate SIGDATA record and calls SIGNAL
(again, on the same stack). SIGNAL prefixes the 18 words with two more -
the class and subclass - and then uses DRESUME to resume execution at one
or other of the 'SIGNAL traps'. In the more common (and the more
interesting) case, control arrives at the label FAIL in BCI or in
ENTER ON USER STACK, with:
- LNB pointing to the appropriate stack frame, so that the
code can find its variables, and
- the address of twenty words of information in the accumulator
(actually in the bottom half of the accumulator, since DRESUME
takes its third parameter, expands it into a descriptor, and
loads it into the accumulator before passing control to the
nominated address).
The twenty words are:
1: Class
2: Subclass
3: SSN/LNB
4: PSR
5: PC
6: SSR
7: SSN/SF
8: IT
9: IC
10: CTB
11: XNB
12: B
13 and 14: DR
15 to 18: Accumulator
19: FPC (for PE only)
20: ?
When control arrives at the label FAIL, the first action is to store
the accumulator, i.e., a descriptor pointing to this twenty-word
area, and then, using the second word of the stored descriptor to
locate the data, NDIAG is called with parameters which are -
the PC at the time of the interrupt;
the LNB at the time of the interrupt;
the value 10;
the class of the interrupt.
NDIAG starts by laying down a new SIGNAL trap in case of further
problems arising during the printing of diagnostics, and also
increments ACTIVE. Then it starts to unravel the stack frames of the
interrupted process. It has checks for stack frames which are totally
implausible - that is, incompatible with the 2900 architecture; for
frames which do not conform to EMAS standards (generated by 'odd'
languages, perhaps); and for frames indicating GLAs in public segments,
corresponding to local controller. For stack frames which pass all these
tests, it extracts the language flag from the indicated GLA, and then
goes through a fairly complicated bit of code to convert the 'class'
(passed in to NDIAG as a parameter) into an 'event' and 'subevent'.
For IMP code, it then calls ONCOND, passing the parameters EVENT,
SUBEVENT and LNB.
ONCOND then works its own way back down the stack frames, looking for an
%ON %EVENT trap. If there is none, ONCOND simply returns to NDIAG
which goes on to print the diagnostics. But if a trap is found, then
ONCOND constructs a double-word of information about the event and
passes control to the nominated place.
Summary
To summarise, when an event occurs, control passes through the following
bits of code:
Director;
routine DIRTRAP;
routine SIGNAL;
one of the SIGNAL traps;
routine NDIAG;
routine ONCOND;
some event trap -
or, if there is none, control returns to NDIAG to print diagnostics.
Example of interrupt trapping
%systemroutinespec ndiag(%integer lnb,pc,fault,inf)
%systemroutinespec reroute contingency(%integer ep,class,
%longinteger mask,%routine on trap,%integername flag)
%systemroutinespec signal(%integer ep,p1,p2,%integername flag)
%systemroutinespec console(%integer ep,%integername st,len)
%externalintegerfnspec readid(%integer adr18)
%externalroutinespec dstop(%integer reason)
%externalintegerfnspec dresume(%integer lnb,pc,adr18)
%externalintegerfnspec ddelay(%integer secs)
%externalstring(15)%fnspec interrupt
%constlonginteger mask=X'FFFFFFFFFFFFFFFF'{the lot}
%externalroutine test trap(%string(255)dummy)
%integer flag,counter
%string(15) multi int
%routine trap (%integer class,subclass)
%conststring(10)%array regs(1:18)="SSN/LNB","PSR ","PC ","SSR ",
"SSN/SF ","IT ","IC ","CTB ","XNB ","B ","DR0 ",
"DR1 ","ACC 0 ","ACC 1 ","ACC 2 ","ACC 3 ","FPC ","? "
%integer flag,adr18
%integerarray aa(1:18)
%if 'a'<=subclass<='z' %then subclass=subclass-'a'+'A'
%if 'W'<=subclass<='Y' %thenstart
%if subclass#'Y' %thenstart;!cannot write if comms failure
printstring("*** Fatal Interrupt ".tostring(subclass)." ***")
newline
printstring("*** Process stops")
newline
%finish
flag=ddelay(2);!to give message time to get out
signal(3,class,subclass,flag);!pass interrupt to subsystem
dstop(100);!we should NEVER get here
%finishelsestart
adr18=addr(aa(1))
flag=readid(adr18)
%if subclass='T' %then console(12,flag,flag) %and ->resume
%if subclass='K' %then console( 7,flag,flag) %and ->resume
%if subclass='C' %then console( 8,flag,flag) %and ->resume
%if subclass='Q' %thenstart
newline
printstring("Registers at event:");newline
%for flag=1,1,18 %cycle
printstring(regs(flag)." ");write(aa(flag),10);newline
%repeat
->resume
%finish
printstring("*** Int:".tostring(subclass)." trapped and ignored")
newline
printstring("*** Use Int:stop to stop")
newline
resume:
flag=dresume(0,0,adr18);!go back to where we were
%finish
%end
reroute contingency(3,65,mask,trap,flag)
!all interrupts of class 65 whose subclass is set in mask (0<=subclass<=63)
reroute contingency(2,65,mask,trap,flag)
!all interrupts of class 65 whose subclass is set in mask (64<=subclass<=127)
!a mask of X'0382000A0382000A' will trap A,C,Q,W,X,Y
%for counter=1,1,120 %cycle
flag=ddelay(1)
multi int=interrupt
%if multi int="stop" %thenexit
%if multi int#"" %start
newline
printstring("*** Multi Character Int:".multi int." inspected ")
newline
printstring("*** Use Int:stop to stop")
newline
printstring("*** Counter = ")
write(counter,1)
newline
%finish
%repeat
reroute contingency(0,0,0,trap{dummy},flag)
!to clear all nominated traps
!without this the next event would be sent to the expected address and
!find the object had been unloaded - disaster!
%end
%endoffile