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